UNPKG

parjs

Version:

Library for building parsers using combinators.

225 lines 7.73 kB
"use strict"; 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