parjs
Version:
Library for building parsers using combinators.
225 lines • 7.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.regexp = exports.ParjserBase = exports.string = exports.defaultDebugFunction = exports.ParserUserState = void 0;
const errors_1 = require("../errors");
const utils_1 = require("../utils");
const result_1 = require("./result");
const state_1 = require("./state");
const wrap_implicit_1 = require("./wrap-implicit");
function getErrorLocation(ps) {
const endln = /\r\n|\n|\r/g;
const { position } = ps;
let lastPos = 0;
let result;
let line = 0;
while ((result = endln.exec(ps.input))) {
if (result.index > position)
break;
lastPos = result.index + result[0].length;
line++;
}
return {
line,
column: line === 0 ? position : position - lastPos
};
}
/** A marker class used for storing the parser's user state. */
class ParserUserState {
}
exports.ParserUserState = ParserUserState;
const defaultDebugFunction = (ps, current, startingPosition) => {
const kindEmoji = {
[result_1.ResultKind.Ok]: "👍🏻 (Ok)",
[result_1.ResultKind.SoftFail]: "😕 (Soft failure)",
[result_1.ResultKind.HardFail]: "😬 (Hard failure)",
[result_1.ResultKind.FatalFail]: "💀 (Fatal failure)"
};
const consumedInput = ps.input.slice(startingPosition, ps.position);
const consumed = [
`consumed '${consumedInput}' (length ${consumedInput.length})`,
`at position ${startingPosition}->${ps.position}`,
kindEmoji[ps.kind],
JSON.stringify(ps, null, 2),
JSON.stringify({
type: current.type,
expecting: current.expecting
}, null, 2)
].join("\n");
console.log(consumed);
};
exports.defaultDebugFunction = defaultDebugFunction;
/**
* Returns a parser that will parse the string `str` and yield the text that was parsed. If it
* can't, it will fail softly without consuming input.
*
* @param str The string to parse.
*/
function string(str) {
return new ParseString(str);
}
exports.string = string;
/**
* The internal base Parjs parser class, which supports only basic parsing operations. Should not be
* used in user code.
*/
class ParjserBase {
expects(expecting) {
const copy = (0, utils_1.clone)(this);
copy.expecting = expecting;
return copy;
}
debug(fn = exports.defaultDebugFunction) {
const copy = (0, utils_1.clone)(this);
copy.debugFunction = fn;
return copy;
}
/**
* Apply the parser to the given state.
*
* @param ps The parsing state.
*/
apply(ps) {
const startingPosition = ps.position;
// @ts-expect-error Check for uninitialized kind.
ps.kind = "Unknown";
ps.reason = undefined;
ps.value = state_1.UNINITIALIZED_RESULT;
this._apply(ps);
// @ts-expect-error Check for uninitialized kind.
if (ps.kind === "Unknown") {
throw new errors_1.ParserDefinitionError(this.type, "the parser's result kind field has not been set.");
}
if (!ps.isOk) {
ps.value = state_1.FAIL_RESULT;
ps.reason = ps.reason || this.expecting;
}
else if (ps.value === state_1.UNINITIALIZED_RESULT) {
throw new errors_1.ParserDefinitionError(this.type, "a parser must set the result's value field if it succeeds.");
}
if (!ps.isOk) {
if (ps.reason == null) {
throw new errors_1.ParserDefinitionError(this.type, "a failure must have a reason");
}
ps.stack.push({
type: this.type,
expecting: this.expecting
});
this.debugFunction?.(ps, this, startingPosition);
}
else {
this.debugFunction?.(ps, this, startingPosition);
ps.stack = [];
}
}
parse(input, initialState) {
if (typeof input !== "string") {
// catches input === undefined, null
throw new TypeError("input must be a valid string");
}
const ps = new state_1.BasicParsingState(input, (0, utils_1.defaults)(new ParserUserState(), initialState));
ps.initialUserState = initialState;
this.apply(ps);
if (ps.isOk) {
if (ps.position !== input.length) {
ps.kind = result_1.ResultKind.SoftFail;
ps.reason = "parsers did not consume all input";
}
}
// @ts-expect-error Check for uninitialized kind.
if (ps.kind === "Unknown") {
throw new Error("Kind was Unknown after parsing finished. This is a bug.");
}
if (ps.kind === result_1.ResultKind.Ok) {
return new result_1.ParjsSuccess(ps.value);
}
else {
const trace = {
userState: ps.userState,
position: ps.position,
reason: ps.reason,
input,
get location() {
return getErrorLocation(ps);
},
stackTrace: ps.stack,
kind: ps.kind
};
return new result_1.ParjsFailure(trace);
}
}
// eslint-disable-next-line max-params
pipe(cmb1, cmb2, cmb3, cmb4, cmb5, cmb6) {
const combinators = [cmb1, cmb2, cmb3, cmb4, cmb5, cmb6].filter(x => x != null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let last = (0, wrap_implicit_1.wrapImplicit)(this);
for (const cmb of combinators) {
last = cmb(last);
}
return last;
}
}
exports.ParjserBase = ParjserBase;
class Regexp extends ParjserBase {
constructor(re) {
super();
this.re = re;
this.type = "regexp";
this.expecting = `expecting input matching '${this.re.source}'`;
}
_apply(ps) {
const { input, position } = ps;
const re = this.re;
re.lastIndex = position;
const match = re.exec(input);
if (!match) {
ps.kind = result_1.ResultKind.SoftFail;
return;
}
ps.position += match[0].length;
ps.value = match.slice();
ps.kind = result_1.ResultKind.Ok;
}
}
/**
* Returns a parser that will try to match the regular expression at the current position and yield
* the result set. If it can't, the parser will fail softly. The match must start at the current
* position. It can't skip any part of the input.
*
* @param origRegexp
*/
function regexp(origRegexp) {
const flags = [origRegexp.ignoreCase && "i", origRegexp.multiline && "m"]
.filter(x => x)
.join("");
const re = new RegExp(origRegexp.source, `${flags}y`);
return new Regexp(re);
}
exports.regexp = regexp;
class ParseString extends ParjserBase {
constructor(str) {
super();
this.str = str;
this.type = "string";
this.expecting = `expecting '${this.str}'`;
}
_apply(ps) {
const { position, input } = ps;
const str = this.str;
if (position + str.length > input.length) {
ps.kind = result_1.ResultKind.SoftFail;
return;
}
// This should create a StringSlice object instead of actually
// copying a whole string.
const substr = input.slice(position, position + str.length);
// Equality test is very very fast.
if (substr !== str) {
ps.kind = result_1.ResultKind.SoftFail;
return;
}
ps.position += str.length;
ps.value = str;
ps.kind = result_1.ResultKind.Ok;
}
}
//# sourceMappingURL=parser.js.map