UNPKG

parsergen-starter

Version:

A complete parser generator starter with PEG.js, optional Moo lexer, and VS Code integration

1,486 lines (1,475 loc) 374 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // node_modules/peggy/lib/grammar-location.js var require_grammar_location = __commonJS({ "node_modules/peggy/lib/grammar-location.js"(exports2, module2) { "use strict"; var _a; var GrammarLocation = (_a = class { /** * Create an instance. * * @param {any} source The original grammarSource. Should be a string or * have a toString() method. * @param {import("./peg").Location} start The starting offset for the * grammar in the larger file. */ constructor(source, start) { this.source = source; this.start = start; } /** * Coerce to a string. * * @returns {string} The source, stringified. */ toString() { return String(this.source); } /** * Return a new Location offset from the given location by the start of the * grammar. * * @param {import("./peg").Location} loc The location as if the start of the * grammar was the start of the file. * @returns {import("./peg").Location} The offset location. */ offset(loc) { return { line: loc.line + this.start.line - 1, column: loc.line === 1 ? loc.column + this.start.column - 1 : loc.column, offset: loc.offset + this.start.offset }; } /** * If the range has a grammarSource that is a GrammarLocation, offset the * start of that range by the GrammarLocation. * * @param {import("./peg").LocationRange} range The range to extract from. * @returns {import("./peg").Location} The offset start if possible, or the * original start. */ static offsetStart(range) { if (range.source && typeof range.source.offset === "function") { return range.source.offset(range.start); } return range.start; } /** * If the range has a grammarSource that is a GrammarLocation, offset the * end of that range by the GrammarLocation. * * @param {import("./peg").LocationRange} range The range to extract from. * @returns {import("./peg").Location} The offset end if possible, or the * original end. */ static offsetEnd(range) { if (range.source && typeof range.source.offset === "function") { return range.source.offset(range.end); } return range.end; } }, __name(_a, "GrammarLocation"), _a); module2.exports = GrammarLocation; } }); // node_modules/peggy/lib/grammar-error.js var require_grammar_error = __commonJS({ "node_modules/peggy/lib/grammar-error.js"(exports2, module2) { "use strict"; var GrammarLocation = require_grammar_location(); var _a; var GrammarError = (_a = class extends SyntaxError { /** * * @param {string} message * @param {PEG.LocationRange} [location] * @param {PEG.DiagnosticNote[]} [diagnostics] */ constructor(message, location, diagnostics) { super(message); this.name = "GrammarError"; this.location = location; if (diagnostics === void 0) { diagnostics = []; } this.diagnostics = diagnostics; this.stage = null; this.problems = [ /** @type {PEG.Problem} */ [ "error", message, location, diagnostics ] ]; } toString() { let str = super.toString(); if (this.location) { str += "\n at "; if (this.location.source !== void 0 && this.location.source !== null) { str += `${this.location.source}:`; } str += `${this.location.start.line}:${this.location.start.column}`; } for (const diag of this.diagnostics) { str += "\n from "; if (diag.location.source !== void 0 && diag.location.source !== null) { str += `${diag.location.source}:`; } str += `${diag.location.start.line}:${diag.location.start.column}: ${diag.message}`; } return str; } /** * Format the error with associated sources. The `location.source` should have * a `toString()` representation in order the result to look nice. If source * is `null` or `undefined`, it is skipped from the output * * Sample output: * ``` * Error: Label "head" is already defined * --> examples/arithmetics.pegjs:15:17 * | * 15 | = head:Factor head:(_ ("*" / "/") _ Factor)* { * | ^^^^ * note: Original label location * --> examples/arithmetics.pegjs:15:5 * | * 15 | = head:Factor head:(_ ("*" / "/") _ Factor)* { * | ^^^^ * ``` * * @param {import("./peg").SourceText[]} sources mapping from location source to source text * * @returns {string} the formatted error */ format(sources) { const srcLines = sources.map(({ source, text }) => ({ source, text: text !== null && text !== void 0 ? String(text).split(/\r\n|\n|\r/g) : [] })); function entry(location, indent, message = "") { let str = ""; const src = srcLines.find(({ source }) => source === location.source); const s = location.start; const offset_s = GrammarLocation.offsetStart(location); if (src) { const e = location.end; const line = src.text[s.line - 1]; const last = s.line === e.line ? e.column : line.length + 1; const hatLen = last - s.column || 1; if (message) { str += ` note: ${message}`; } str += ` --> ${location.source}:${offset_s.line}:${offset_s.column} ${"".padEnd(indent)} | ${offset_s.line.toString().padStart(indent)} | ${line} ${"".padEnd(indent)} | ${"".padEnd(s.column - 1)}${"".padEnd(hatLen, "^")}`; } else { str += ` at ${location.source}:${offset_s.line}:${offset_s.column}`; if (message) { str += `: ${message}`; } } return str; } __name(entry, "entry"); function formatProblem(severity, message, location, diagnostics = []) { let maxLine = -Infinity; if (location) { maxLine = diagnostics.reduce((t, { location: location2 }) => Math.max(t, GrammarLocation.offsetStart(location2).line), location.start.line); } else { maxLine = Math.max.apply(null, diagnostics.map((d) => d.location.start.line)); } maxLine = maxLine.toString().length; let str = `${severity}: ${message}`; if (location) { str += entry(location, maxLine); } for (const diag of diagnostics) { str += entry(diag.location, maxLine, diag.message); } return str; } __name(formatProblem, "formatProblem"); return this.problems.filter((p) => p[0] !== "info").map((p) => formatProblem(...p)).join("\n\n"); } }, __name(_a, "GrammarError"), _a); module2.exports = GrammarError; } }); // node_modules/peggy/lib/compiler/visitor.js var require_visitor = __commonJS({ "node_modules/peggy/lib/compiler/visitor.js"(exports2, module2) { "use strict"; var visitor2 = { build(functions) { function visit(node, ...args) { return functions[node.type](node, ...args); } __name(visit, "visit"); function visitNop() { } __name(visitNop, "visitNop"); function visitExpression(node, ...args) { return visit(node.expression, ...args); } __name(visitExpression, "visitExpression"); function visitChildren(property) { return function(node, ...args) { node[property].forEach((child) => visit(child, ...args)); }; } __name(visitChildren, "visitChildren"); const DEFAULT_FUNCTIONS = { grammar(node, ...args) { for (const imp of node.imports) { visit(imp, ...args); } if (node.topLevelInitializer) { if (Array.isArray(node.topLevelInitializer)) { for (const tli of node.topLevelInitializer) { visit(tli, ...args); } } else { visit(node.topLevelInitializer, ...args); } } if (node.initializer) { if (Array.isArray(node.initializer)) { for (const init of node.initializer) { visit(init, ...args); } } else { visit(node.initializer, ...args); } } node.rules.forEach((rule) => visit(rule, ...args)); }, grammar_import: visitNop, top_level_initializer: visitNop, initializer: visitNop, rule: visitExpression, named: visitExpression, choice: visitChildren("alternatives"), action: visitExpression, sequence: visitChildren("elements"), labeled: visitExpression, text: visitExpression, simple_and: visitExpression, simple_not: visitExpression, optional: visitExpression, zero_or_more: visitExpression, one_or_more: visitExpression, repeated(node, ...args) { if (node.delimiter) { visit(node.delimiter, ...args); } return visit(node.expression, ...args); }, group: visitExpression, semantic_and: visitNop, semantic_not: visitNop, rule_ref: visitNop, library_ref: visitNop, literal: visitNop, class: visitNop, any: visitNop }; Object.keys(DEFAULT_FUNCTIONS).forEach((type) => { if (!Object.prototype.hasOwnProperty.call(functions, type)) { functions[type] = DEFAULT_FUNCTIONS[type]; } }); return visit; } }; module2.exports = visitor2; } }); // node_modules/peggy/lib/compiler/asts.js var require_asts = __commonJS({ "node_modules/peggy/lib/compiler/asts.js"(exports2, module2) { "use strict"; var visitor2 = require_visitor(); function combinePossibleArrays(a, b) { if (!(a && b)) { return a || b; } const aa = Array.isArray(a) ? a : [ a ]; aa.push(b); return aa; } __name(combinePossibleArrays, "combinePossibleArrays"); var asts = { /** * Find the rule with the given name, if it exists. * * @param {PEG.ast.Grammar} ast * @param {string} name * @returns {PEG.ast.Rule | undefined} */ findRule(ast2, name) { for (let i = 0; i < ast2.rules.length; i++) { if (ast2.rules[i].name === name) { return ast2.rules[i]; } } return void 0; }, /** * Find the index of the rule with the given name, if it exists. * Otherwise returns -1. * * @param {PEG.ast.Grammar} ast * @param {string} name * @returns {number} */ indexOfRule(ast2, name) { for (let i = 0; i < ast2.rules.length; i++) { if (ast2.rules[i].name === name) { return i; } } return -1; }, alwaysConsumesOnSuccess(ast2, node) { function consumesTrue() { return true; } __name(consumesTrue, "consumesTrue"); function consumesFalse() { return false; } __name(consumesFalse, "consumesFalse"); const consumes = visitor2.build({ choice(node2) { return node2.alternatives.every(consumes); }, sequence(node2) { return node2.elements.some(consumes); }, simple_and: consumesFalse, simple_not: consumesFalse, optional: consumesFalse, zero_or_more: consumesFalse, repeated(node2) { const min = node2.min ? node2.min : node2.max; if (min.type !== "constant" || min.value === 0) { return false; } if (consumes(node2.expression)) { return true; } if (min.value > 1 && node2.delimiter && consumes(node2.delimiter)) { return true; } return false; }, semantic_and: consumesFalse, semantic_not: consumesFalse, rule_ref(node2) { const rule = asts.findRule(ast2, node2.name); return rule ? consumes(rule) : void 0; }, // No way to know for external rules. library_ref: consumesFalse, literal(node2) { return node2.value !== ""; }, class: consumesTrue, any: consumesTrue }); return consumes(node); }, combine(asts2) { return asts2.reduce((combined, ast2) => { combined.topLevelInitializer = combinePossibleArrays(combined.topLevelInitializer, ast2.topLevelInitializer); combined.initializer = combinePossibleArrays(combined.initializer, ast2.initializer); combined.rules = combined.rules.concat(ast2.rules); return combined; }); } }; module2.exports = asts; } }); // node_modules/peggy/lib/compiler/passes/add-imported-rules.js var require_add_imported_rules = __commonJS({ "node_modules/peggy/lib/compiler/passes/add-imported-rules.js"(exports2, module2) { "use strict"; function addImportedRules2(ast2) { let libraryNumber = 0; for (const imp of ast2.imports) { for (const what of imp.what) { let original = void 0; switch (what.type) { case "import_binding_all": continue; case "import_binding_default": break; case "import_binding": original = what.binding; break; case "import_binding_rename": original = what.rename; break; default: throw new TypeError("Unknown binding type"); } ast2.rules.push({ type: "rule", name: what.binding, nameLocation: what.location, expression: { type: "library_ref", name: original, library: imp.from.module, libraryNumber, location: what.location }, location: imp.from.location }); } libraryNumber++; } } __name(addImportedRules2, "addImportedRules"); module2.exports = addImportedRules2; } }); // node_modules/peggy/lib/compiler/passes/fix-library-numbers.js var require_fix_library_numbers = __commonJS({ "node_modules/peggy/lib/compiler/passes/fix-library-numbers.js"(exports2, module2) { "use strict"; var visitor2 = require_visitor(); function findLibraryNumber(ast2, name) { let libraryNumber = 0; for (const imp of ast2.imports) { for (const what of imp.what) { if (what.type === "import_binding_all" && what.binding === name) { return libraryNumber; } } libraryNumber++; } return -1; } __name(findLibraryNumber, "findLibraryNumber"); function fixLibraryNumbers2(ast2, _options, session2) { const check = visitor2.build({ library_ref(node) { if (node.libraryNumber === -1) { node.libraryNumber = findLibraryNumber(ast2, node.library); if (node.libraryNumber === -1) { session2.error(`Unknown module "${node.library}"`, node.location); } } } }); check(ast2); } __name(fixLibraryNumbers2, "fixLibraryNumbers"); module2.exports = fixLibraryNumbers2; } }); // node_modules/peggy/lib/compiler/opcodes.js var require_opcodes = __commonJS({ "node_modules/peggy/lib/compiler/opcodes.js"(exports2, module2) { "use strict"; var opcodes = { // Stack Manipulation /** @deprecated Unused */ PUSH: 0, PUSH_EMPTY_STRING: 35, PUSH_UNDEFINED: 1, PUSH_NULL: 2, PUSH_FAILED: 3, PUSH_EMPTY_ARRAY: 4, PUSH_CURR_POS: 5, POP: 6, POP_CURR_POS: 7, POP_N: 8, NIP: 9, APPEND: 10, WRAP: 11, TEXT: 12, PLUCK: 36, // Conditions and Loops IF: 13, IF_ERROR: 14, IF_NOT_ERROR: 15, IF_LT: 30, IF_GE: 31, IF_LT_DYNAMIC: 32, IF_GE_DYNAMIC: 33, WHILE_NOT_ERROR: 16, // Matching MATCH_ANY: 17, MATCH_STRING: 18, MATCH_STRING_IC: 19, MATCH_CHAR_CLASS: 20, MATCH_UNICODE_CLASS: 42, /** @deprecated Replaced with `MATCH_CHAR_CLASS` */ MATCH_REGEXP: 20, ACCEPT_N: 21, ACCEPT_STRING: 22, FAIL: 23, // Calls LOAD_SAVED_POS: 24, UPDATE_SAVED_POS: 25, CALL: 26, // Rules RULE: 27, LIBRARY_RULE: 41, // Failure Reporting SILENT_FAILS_ON: 28, SILENT_FAILS_OFF: 29, // Because the tests have hard-coded opcode numbers, don't renumber // existing opcodes. New opcodes that have been put in the correct // sections above are repeated here in order to ensure we don't // reuse them. // // IF_LT: 30 // IF_GE: 31 // IF_LT_DYNAMIC: 32 // IF_GE_DYNAMIC: 33 // 34 reserved for @mingun // PUSH_EMPTY_STRING: 35 // PLUCK: 36 SOURCE_MAP_PUSH: 37, SOURCE_MAP_POP: 38, SOURCE_MAP_LABEL_PUSH: 39, SOURCE_MAP_LABEL_POP: 40 }; module2.exports = opcodes; } }); // node_modules/peggy/lib/compiler/intern.js var require_intern = __commonJS({ "node_modules/peggy/lib/compiler/intern.js"(exports2, module2) { "use strict"; var _a; var Intern = (_a = class { /** * @typedef {object} InternOptions * @property {(input: V) => string} [stringify=String] Represent the * converted input as a string, for value comparison. * @property {(input: T) => V} [convert=(x) => x] Convert the input to its * stored form. Required if type V is not the same as type T. Return * falsy value to have this input not be added; add() will return -1 in * this case. */ /** * @param {InternOptions} [options] */ constructor(options2) { this.options = { stringify: String, convert: /* @__PURE__ */ __name((x) => ( /** @type {unknown} */ x ), "convert"), ...options2 }; this.items = []; this.offsets = /* @__PURE__ */ Object.create(null); } /** * Intern an item, getting it's asssociated number. Returns -1 for falsy * inputs. O(1) with constants tied to the convert and stringify options. * * @param {T} input * @return {number} */ add(input) { const c = this.options.convert(input); if (!c) { return -1; } const s = this.options.stringify(c); let num = this.offsets[s]; if (num === void 0) { num = this.items.push(c) - 1; this.offsets[s] = num; } return num; } /** * @param {number} i * @returns {V} */ get(i) { return this.items[i]; } /** * @template U * @param {(value: V, index: number, array: V[]) => U} fn * @returns {U[]} */ map(fn) { return this.items.map(fn); } }, __name(_a, "Intern"), _a); module2.exports = Intern; } }); // node_modules/peggy/lib/compiler/passes/inference-match-result.js var require_inference_match_result = __commonJS({ "node_modules/peggy/lib/compiler/passes/inference-match-result.js"(exports2, module2) { "use strict"; var visitor2 = require_visitor(); var asts = require_asts(); var GrammarError = require_grammar_error(); var ALWAYS_MATCH = 1; var SOMETIMES_MATCH = 0; var NEVER_MATCH = -1; function inferenceMatchResult2(ast2) { function sometimesMatch(node) { return node.match = SOMETIMES_MATCH; } __name(sometimesMatch, "sometimesMatch"); function alwaysMatch(node) { inference(node.expression); return node.match = ALWAYS_MATCH; } __name(alwaysMatch, "alwaysMatch"); function inferenceExpression(node) { return node.match = inference(node.expression); } __name(inferenceExpression, "inferenceExpression"); function inferenceElements(elements, forChoice) { const length = elements.length; let always = 0; let never = 0; for (let i = 0; i < length; ++i) { const result = inference(elements[i]); if (result === ALWAYS_MATCH) { ++always; } if (result === NEVER_MATCH) { ++never; } } if (always === length) { return ALWAYS_MATCH; } if (forChoice) { return never === length ? NEVER_MATCH : SOMETIMES_MATCH; } return never > 0 ? NEVER_MATCH : SOMETIMES_MATCH; } __name(inferenceElements, "inferenceElements"); const inference = visitor2.build({ rule(node) { let oldResult; let count = 0; if (typeof node.match === "undefined") { node.match = SOMETIMES_MATCH; do { oldResult = node.match; node.match = inference(node.expression); if (++count > 6) { throw new GrammarError("Infinity cycle detected when trying to evaluate node match result", node.location); } } while (oldResult !== node.match); } return node.match; }, named: inferenceExpression, choice(node) { return node.match = inferenceElements(node.alternatives, true); }, action: inferenceExpression, sequence(node) { return node.match = inferenceElements(node.elements, false); }, labeled: inferenceExpression, text: inferenceExpression, simple_and: inferenceExpression, simple_not(node) { return node.match = -inference(node.expression); }, optional: alwaysMatch, zero_or_more: alwaysMatch, one_or_more: inferenceExpression, repeated(node) { const match = inference(node.expression); const dMatch = node.delimiter ? inference(node.delimiter) : NEVER_MATCH; const min = node.min ? node.min : node.max; if (min.type !== "constant" || node.max.type !== "constant") { return node.match = SOMETIMES_MATCH; } if (node.max.value === 0 || node.max.value !== null && min.value > node.max.value) { return node.match = NEVER_MATCH; } if (match === NEVER_MATCH) { return node.match = min.value === 0 ? ALWAYS_MATCH : NEVER_MATCH; } if (match === ALWAYS_MATCH) { if (node.delimiter && min.value >= 2) { return node.match = dMatch; } return node.match = ALWAYS_MATCH; } if (node.delimiter && min.value >= 2) { return ( // If a delimiter never match then the range also never match (because // there at least one delimiter) node.match = dMatch === NEVER_MATCH ? NEVER_MATCH : SOMETIMES_MATCH ); } return node.match = min.value === 0 ? ALWAYS_MATCH : SOMETIMES_MATCH; }, group: inferenceExpression, semantic_and: sometimesMatch, semantic_not: sometimesMatch, rule_ref(node) { const rule = asts.findRule(ast2, node.name); if (!rule) { return SOMETIMES_MATCH; } return node.match = inference(rule); }, library_ref() { return 0; }, literal(node) { const match = node.value.length === 0 ? ALWAYS_MATCH : SOMETIMES_MATCH; return node.match = match; }, class(node) { const match = node.parts.length === 0 ? NEVER_MATCH : SOMETIMES_MATCH; return node.match = match; }, // |any| not match on empty input any: sometimesMatch }); inference(ast2); } __name(inferenceMatchResult2, "inferenceMatchResult"); inferenceMatchResult2.ALWAYS_MATCH = ALWAYS_MATCH; inferenceMatchResult2.SOMETIMES_MATCH = SOMETIMES_MATCH; inferenceMatchResult2.NEVER_MATCH = NEVER_MATCH; module2.exports = inferenceMatchResult2; } }); // node_modules/peggy/lib/compiler/passes/generate-bytecode.js var require_generate_bytecode = __commonJS({ "node_modules/peggy/lib/compiler/passes/generate-bytecode.js"(exports2, module2) { "use strict"; var asts = require_asts(); var op = require_opcodes(); var visitor2 = require_visitor(); var Intern = require_intern(); var { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require_inference_match_result(); function generateBytecode2(ast2, options2) { const literals = new Intern(); const classes = new Intern({ stringify: JSON.stringify, /** @type {(input: PEG.ast.CharacterClass) => PEG.ast.GrammarCharacterClass} */ convert: /* @__PURE__ */ __name((node) => ({ value: node.parts, inverted: node.inverted, ignoreCase: node.ignoreCase, unicode: node.unicode }), "convert") }); const expectations = new Intern({ stringify: JSON.stringify }); const importedNames = new Intern(); const functions = []; const locations = []; function addFunctionConst(predicate, params, node) { const func = { predicate, params, body: node.code, location: node.codeLocation }; const pattern = JSON.stringify(func); const index = functions.findIndex((f) => JSON.stringify(f) === pattern); return index === -1 ? functions.push(func) - 1 : index; } __name(addFunctionConst, "addFunctionConst"); function addLocation(location) { return locations.push(location) - 1; } __name(addLocation, "addLocation"); function cloneEnv(env) { const clone = {}; Object.keys(env).forEach((name) => { clone[name] = env[name]; }); return clone; } __name(cloneEnv, "cloneEnv"); function buildSequence(first, ...args) { return first.concat(...args); } __name(buildSequence, "buildSequence"); function buildCondition(match, condCode, thenCode, elseCode) { if (match === ALWAYS_MATCH) { return thenCode; } if (match === NEVER_MATCH) { return elseCode; } return condCode.concat([ thenCode.length, elseCode.length ], thenCode, elseCode); } __name(buildCondition, "buildCondition"); function buildLoop(condCode, bodyCode) { return condCode.concat([ bodyCode.length ], bodyCode); } __name(buildLoop, "buildLoop"); function buildCall(functionIndex, delta, env, sp) { const params = Object.keys(env).map((name) => sp - env[name]); return [ op.CALL, functionIndex, delta, params.length ].concat(params); } __name(buildCall, "buildCall"); function buildSimplePredicate(expression, negative, context) { const match = expression.match || 0; return buildSequence( [ op.PUSH_CURR_POS ], [ op.SILENT_FAILS_ON ], // eslint-disable-next-line no-use-before-define -- Mutual recursion generate2(expression, { sp: context.sp + 1, env: cloneEnv(context.env), action: null }), [ op.SILENT_FAILS_OFF ], buildCondition(negative ? -match : match, [ negative ? op.IF_ERROR : op.IF_NOT_ERROR ], buildSequence([ op.POP ], [ negative ? op.POP : op.POP_CURR_POS ], [ op.PUSH_UNDEFINED ]), buildSequence([ op.POP ], [ negative ? op.POP_CURR_POS : op.POP ], [ op.PUSH_FAILED ])) ); } __name(buildSimplePredicate, "buildSimplePredicate"); function buildSemanticPredicate(node, negative, context) { const functionIndex = addFunctionConst(true, Object.keys(context.env), node); return buildSequence([ op.UPDATE_SAVED_POS ], buildCall(functionIndex, 0, context.env, context.sp), buildCondition(node.match || 0, [ op.IF ], buildSequence([ op.POP ], negative ? [ op.PUSH_FAILED ] : [ op.PUSH_UNDEFINED ]), buildSequence([ op.POP ], negative ? [ op.PUSH_UNDEFINED ] : [ op.PUSH_FAILED ]))); } __name(buildSemanticPredicate, "buildSemanticPredicate"); function buildAppendLoop(expressionCode) { return buildLoop([ op.WHILE_NOT_ERROR ], buildSequence([ op.APPEND ], expressionCode)); } __name(buildAppendLoop, "buildAppendLoop"); function unknownBoundary(boundary) { const b = ( /** @type {{ type: string }} */ boundary ); return new Error(`Unknown boundary type "${b.type}" for the "repeated" node`); } __name(unknownBoundary, "unknownBoundary"); function buildRangeCall(boundary, env, sp, offset) { switch (boundary.type) { case "constant": return { pre: [], post: [], sp }; case "variable": boundary.sp = offset + sp - env[boundary.value]; return { pre: [], post: [], sp }; case "function": { boundary.sp = offset; const functionIndex = addFunctionConst(true, Object.keys(env), { code: boundary.value, codeLocation: boundary.codeLocation }); return { pre: buildCall(functionIndex, 0, env, sp), post: [ op.NIP ], // +1 for the function result sp: sp + 1 }; } // istanbul ignore next Because we never generate invalid boundary type we cannot reach this branch default: throw unknownBoundary(boundary); } } __name(buildRangeCall, "buildRangeCall"); function buildCheckMax(expressionCode, max) { if (max.value !== null) { const checkCode = max.type === "constant" ? [ op.IF_GE, max.value ] : [ op.IF_GE_DYNAMIC, max.sp || 0 ]; return buildCondition( SOMETIMES_MATCH, checkCode, [ op.PUSH_FAILED ], expressionCode // else ); } return expressionCode; } __name(buildCheckMax, "buildCheckMax"); function buildCheckMin(expressionCode, min) { const checkCode = min.type === "constant" ? [ op.IF_LT, min.value ] : [ op.IF_LT_DYNAMIC, min.sp || 0 ]; return buildSequence(expressionCode, buildCondition( SOMETIMES_MATCH, checkCode, /* eslint-disable @stylistic/indent -- Clarity */ [ op.POP, op.POP_CURR_POS, op.PUSH_FAILED ], /* eslint-enable @stylistic/indent */ [ op.NIP ] // } stack:[ [elem...] ] )); } __name(buildCheckMin, "buildCheckMin"); function buildRangeBody(delimiterNode, expressionMatch, expressionCode, context, offset) { if (delimiterNode) { return buildSequence( [ op.PUSH_CURR_POS ], // eslint-disable-next-line no-use-before-define -- Mutual recursion generate2(delimiterNode, { // +1 for the saved offset sp: context.sp + offset + 1, env: cloneEnv(context.env), action: null }), buildCondition( delimiterNode.match || 0, [ op.IF_NOT_ERROR ], buildSequence([ op.POP ], expressionCode, buildCondition( -expressionMatch, [ op.IF_ERROR ], // If element FAILED, rollback currPos to saved value. /* eslint-disable @stylistic/indent -- Clarity */ [ op.POP, op.POP_CURR_POS, op.PUSH_FAILED ], /* eslint-enable @stylistic/indent */ // Else, just drop saved currPos. [ op.NIP ] // } stack:[ item ] )), // If delimiter FAILED, currPos not changed, so just drop it. [ op.NIP ] // stack:[ peg$FAILED ] ) // stack:[ <?> ] ); } return expressionCode; } __name(buildRangeBody, "buildRangeBody"); function wrapGenerators(generators) { if (options2 && options2.output === "source-and-map") { Object.keys(generators).forEach((name) => { const generator = generators[name]; generators[name] = function(node, ...args) { const generated = generator(node, ...args); if (generated === void 0 || !node.location) { return generated; } return buildSequence([ op.SOURCE_MAP_PUSH, addLocation(node.location) ], generated, [ op.SOURCE_MAP_POP ]); }; }); } return visitor2.build(generators); } __name(wrapGenerators, "wrapGenerators"); const generate2 = wrapGenerators({ grammar(node) { node.rules.forEach(generate2); node.literals = literals.items; node.classes = classes.items; node.expectations = expectations.items; node.importedNames = importedNames.items; node.functions = functions; node.locations = locations; }, rule(node) { node.bytecode = generate2(node.expression, { sp: -1, env: {}, pluck: [], action: null }); }, named(node, context) { const match = node.match || 0; const nameIndex = match === ALWAYS_MATCH ? -1 : expectations.add({ type: "rule", value: node.name }); return buildSequence([ op.SILENT_FAILS_ON ], generate2(node.expression, context), [ op.SILENT_FAILS_OFF ], buildCondition(-match, [ op.IF_ERROR ], [ op.FAIL, nameIndex ], [])); }, choice(node, context) { function buildAlternativesCode(alternatives, context2) { const match = alternatives[0].match || 0; const first = generate2(alternatives[0], { sp: context2.sp, env: cloneEnv(context2.env), action: null }); if (match === ALWAYS_MATCH) { return first; } return buildSequence(first, alternatives.length > 1 ? buildCondition(SOMETIMES_MATCH, [ op.IF_ERROR ], buildSequence([ op.POP ], buildAlternativesCode(alternatives.slice(1), context2)), []) : []); } __name(buildAlternativesCode, "buildAlternativesCode"); return buildAlternativesCode(node.alternatives, context); }, action(node, context) { const env = cloneEnv(context.env); const emitCall = node.expression.type !== "sequence" || node.expression.elements.length === 0; const expressionCode = generate2(node.expression, { sp: context.sp + (emitCall ? 1 : 0), env, action: node }); const match = node.expression.match || 0; const functionIndex = emitCall && match !== NEVER_MATCH ? addFunctionConst(false, Object.keys(env), node) : -1; return emitCall ? buildSequence([ op.PUSH_CURR_POS ], expressionCode, buildCondition(match, [ op.IF_NOT_ERROR ], buildSequence([ op.LOAD_SAVED_POS, 1 ], buildCall(functionIndex, 1, env, context.sp + 2)), []), [ op.NIP ]) : expressionCode; }, sequence(node, context) { function buildElementsCode(elements, context2) { if (elements.length > 0) { const processedCount = node.elements.length - elements.length + 1; return buildSequence(generate2(elements[0], { sp: context2.sp, env: context2.env, pluck: context2.pluck, action: null }), buildCondition(elements[0].match || 0, [ op.IF_NOT_ERROR ], buildElementsCode(elements.slice(1), { sp: context2.sp + 1, env: context2.env, pluck: context2.pluck, action: context2.action }), buildSequence(processedCount > 1 ? [ op.POP_N, processedCount ] : [ op.POP ], [ op.POP_CURR_POS ], [ op.PUSH_FAILED ]))); } else { if (context2.pluck && context2.pluck.length > 0) { return buildSequence([ op.PLUCK, node.elements.length + 1, context2.pluck.length ], context2.pluck.map((eSP) => context2.sp - eSP)); } if (context2.action) { const functionIndex = addFunctionConst(false, Object.keys(context2.env), context2.action); return buildSequence([ op.LOAD_SAVED_POS, node.elements.length ], buildCall(functionIndex, node.elements.length + 1, context2.env, context2.sp)); } else { return buildSequence([ op.WRAP, node.elements.length ], [ op.NIP ]); } } } __name(buildElementsCode, "buildElementsCode"); return buildSequence([ op.PUSH_CURR_POS ], buildElementsCode(node.elements, { sp: context.sp + 1, env: context.env, pluck: [], action: context.action })); }, labeled(node, context) { let env = context.env; const label = node.label; const sp = context.sp + 1; if (label) { env = cloneEnv(context.env); context.env[label] = sp; } if (node.pick) { context.pluck.push(sp); } const expression = generate2(node.expression, { sp: context.sp, env, action: null }); if (label && node.labelLocation && options2 && options2.output === "source-and-map") { return buildSequence([ op.SOURCE_MAP_LABEL_PUSH, sp, literals.add(label), addLocation(node.labelLocation) ], expression, [ op.SOURCE_MAP_LABEL_POP, sp ]); } return expression; }, text(node, context) { return buildSequence([ op.PUSH_CURR_POS ], generate2(node.expression, { sp: context.sp + 1, env: cloneEnv(context.env), action: null }), buildCondition(node.match || 0, [ op.IF_NOT_ERROR ], buildSequence([ op.POP ], [ op.TEXT ]), [ op.NIP ])); }, simple_and(node, context) { return buildSimplePredicate(node.expression, false, context); }, simple_not(node, context) { return buildSimplePredicate(node.expression, true, context); }, optional(node, context) { return buildSequence(generate2(node.expression, { sp: context.sp, env: cloneEnv(context.env), action: null }), buildCondition( // Check expression match, not the node match // If expression always match, no need to replace FAILED to NULL, // because FAILED will never appeared -(node.expression.match || 0), [ op.IF_ERROR ], buildSequence([ op.POP ], [ op.PUSH_NULL ]), [] )); }, zero_or_more(node, context) { const expressionCode = generate2(node.expression, { sp: context.sp + 1, env: cloneEnv(context.env), action: null }); return buildSequence([ op.PUSH_EMPTY_ARRAY ], expressionCode, buildAppendLoop(expressionCode), [ op.POP ]); }, one_or_more(node, context) { const expressionCode = generate2(node.expression, { sp: context.sp + 1, env: cloneEnv(context.env), action: null }); return buildSequence([ op.PUSH_EMPTY_ARRAY ], expressionCode, buildCondition( // Condition depends on the expression match, not the node match node.expression.match || 0, [ op.IF_NOT_ERROR ], buildSequence(buildAppendLoop(expressionCode), [ op.POP ]), buildSequence([ op.POP ], [ op.POP ], [ op.PUSH_FAILED ]) )); }, repeated(node, context) { const min = node.min ? node.min : node.max; const hasMin = min.type !== "constant" || min.value > 0; const hasBoundedMax = node.max.type !== "constant" && node.max.value !== null; const offset = hasMin ? 2 : 1; const minCode = node.min ? buildRangeCall( node.min, context.env, context.sp, // +1 for the result slot with an array // +1 for the saved position // +1 if we have a "function" maximum it occupies an additional slot in the stack 2 + (node.max.type === "function" ? 1 : 0) ) : { pre: [], post: [], sp: context.sp }; const maxCode = buildRangeCall(node.max, context.env, minCode.sp, offset); const firstExpressionCode = generate2(node.expression, { sp: maxCode.sp + offset, env: cloneEnv(context.env), action: null }); const expressionCode = node.delimiter !== null ? generate2(node.expression, { // +1 for the saved position before parsing the `delimiter elem` pair sp: maxCode.sp + offset + 1, env: cloneEnv(context.env), action: null }) : firstExpressionCode; const bodyCode = buildRangeBody(node.delimiter, node.expression.match || 0, expressionCode, context, offset); const checkMaxCode = buildCheckMax(bodyCode, node.max); const firstElemCode = hasBoundedMax ? buildCheckMax(firstExpressionCode, node.max) : firstExpressionCode; const mainLoopCode = buildSequence( // If the low boundary present, then backtracking is possible, so save the current pos hasMin ? [ op.PUSH_CURR_POS ] : [], [ op.PUSH_EMPTY_ARRAY ], firstElemCode, buildAppendLoop(checkMaxCode), [ op.POP ] // stack:[ pos, [...] ] (pop elem===`peg$FAILED`) ); return buildSequence( minCode.pre, maxCode.pre, // Check the low boundary, if it is defined and not |0|. hasMin ? buildCheckMin(mainLoopCode, min) : mainLoopCode, maxCode.post, minCode.post ); }, group(node, context) { return generate2(node.expression, { sp: context.sp, env: cloneEnv(context.env), action: null }); }, semantic_and(node, context) { return buildSemanticPredicate(node, false, context); }, semantic_not(node, context) { return buildSemanticPredicate(node, true, context); }, rule_ref(node) { return [ op.RULE, asts.indexOfRule(ast2, node.name) ]; }, library_ref(node) { return [ op.LIBRARY_RULE, node.libraryNumber, importedNames.add(node.name) ]; }, literal(node) { if (node.value.length > 0) { const match = node.match || 0; const needConst = match === SOMETIMES_MATCH || match === ALWAYS_MATCH && !node.ignoreCase; const stringIndex = needConst ? literals.add(node.ignoreCase ? node.value.toLowerCase() : node.value) : -1; const expectedIndex = match !== ALWAYS_MATCH ? expectations.add({ type: "literal", value: node.value, ignoreCase: node.ignoreCase }) : -1; return buildCondition(match, node.ignoreCase ? [ op.MATCH_STRING_IC, stringIndex ] : [ op.MATCH_STRING, stringIndex ], node.ignoreCase ? [ op.ACCEPT_N, node.value.length ] : [ op.ACCEPT_STRING, stringIndex ], [ op.FAIL, expectedIndex ]); } return [ op.PUSH_EMPTY_STRING ]; }, class(node) { const match = node.match || 0; const classIndex = match === SOMETIMES_MATCH ? classes.add(node) : -1; const expectedIndex = match !== ALWAYS_MATCH ? expectations.add({ type: "class", value: node.parts, inverted: node.inverted, ignoreCase: node.ignoreCase, unicode: node.unicode }) : -1; return buildCondition(match, [ node.unicode ? op.MATCH_UNICODE_CLASS : op.MATCH_CHAR_CLASS,