UNPKG

parsergen-starter

Version:

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

1 lines 688 kB
{"version":3,"sources":["../node_modules/peggy/lib/grammar-location.js","../node_modules/peggy/lib/grammar-error.js","../node_modules/peggy/lib/compiler/visitor.js","../node_modules/peggy/lib/compiler/asts.js","../node_modules/peggy/lib/compiler/passes/add-imported-rules.js","../node_modules/peggy/lib/compiler/passes/fix-library-numbers.js","../node_modules/peggy/lib/compiler/opcodes.js","../node_modules/peggy/lib/compiler/intern.js","../node_modules/peggy/lib/compiler/passes/inference-match-result.js","../node_modules/peggy/lib/compiler/passes/generate-bytecode.js","../node_modules/source-map-generator/lib/base64.js","../node_modules/source-map-generator/lib/base64-vlq.js","../node_modules/source-map-generator/lib/util.js","../node_modules/source-map-generator/lib/array-set.js","../node_modules/source-map-generator/lib/mapping-list.js","../node_modules/source-map-generator/lib/source-map-generator.js","../node_modules/source-map-generator/lib/source-node.js","../node_modules/source-map-generator/source-map.js","../node_modules/peggy/lib/compiler/stack.js","../node_modules/peggy/lib/version.js","../node_modules/peggy/lib/compiler/utils.js","../node_modules/peggy/lib/parser.js","../node_modules/peggy/lib/compiler/passes/generate-js.js","../node_modules/peggy/lib/compiler/passes/remove-proxy-rules.js","../node_modules/peggy/lib/compiler/passes/merge-character-classes.js","../node_modules/peggy/lib/compiler/passes/remove-unused-rules.js","../node_modules/peggy/lib/compiler/passes/report-duplicate-imports.js","../node_modules/peggy/lib/compiler/passes/report-duplicate-labels.js","../node_modules/peggy/lib/compiler/passes/report-duplicate-rules.js","../node_modules/peggy/lib/compiler/passes/report-infinite-recursion.js","../node_modules/peggy/lib/compiler/passes/report-infinite-repetition.js","../node_modules/peggy/lib/compiler/passes/report-undefined-rules.js","../node_modules/peggy/lib/compiler/passes/report-incorrect-plucking.js","../node_modules/peggy/lib/compiler/passes/report-unreachable.js","../node_modules/peggy/lib/compiler/session.js","../node_modules/peggy/lib/compiler/index.js","../node_modules/peggy/lib/peg.js","../src/index.ts","../src/utils/highlight.ts","../src/utils/format.ts","../src/utils/ast.ts","../src/grammar/index.ts","../src/lexer/index.ts","../src/parser/index.ts"],"sourcesContent":["\"use strict\";\n\n/**\n * When used as a grammarSource, allows grammars embedded in larger files to\n * specify their offset. The start location is the first character in the\n * grammar. The first line is often moved to the right by some number of\n * columns, but subsequent lines all start at the first column.\n */\nclass GrammarLocation {\n /**\n * Create an instance.\n *\n * @param {any} source The original grammarSource. Should be a string or\n * have a toString() method.\n * @param {import(\"./peg\").Location} start The starting offset for the\n * grammar in the larger file.\n */\n constructor(source, start) {\n this.source = source;\n this.start = start;\n }\n\n /**\n * Coerce to a string.\n *\n * @returns {string} The source, stringified.\n */\n toString() {\n return String(this.source);\n }\n\n /**\n * Return a new Location offset from the given location by the start of the\n * grammar.\n *\n * @param {import(\"./peg\").Location} loc The location as if the start of the\n * grammar was the start of the file.\n * @returns {import(\"./peg\").Location} The offset location.\n */\n offset(loc) {\n return {\n line: loc.line + this.start.line - 1,\n column: (loc.line === 1)\n ? loc.column + this.start.column - 1\n : loc.column,\n offset: loc.offset + this.start.offset,\n };\n }\n\n /**\n * If the range has a grammarSource that is a GrammarLocation, offset the\n * start of that range by the GrammarLocation.\n *\n * @param {import(\"./peg\").LocationRange} range The range to extract from.\n * @returns {import(\"./peg\").Location} The offset start if possible, or the\n * original start.\n */\n static offsetStart(range) {\n if (range.source && (typeof range.source.offset === \"function\")) {\n return range.source.offset(range.start);\n }\n return range.start;\n }\n\n /**\n * If the range has a grammarSource that is a GrammarLocation, offset the\n * end of that range by the GrammarLocation.\n *\n * @param {import(\"./peg\").LocationRange} range The range to extract from.\n * @returns {import(\"./peg\").Location} The offset end if possible, or the\n * original end.\n */\n static offsetEnd(range) {\n if (range.source && (typeof range.source.offset === \"function\")) {\n return range.source.offset(range.end);\n }\n return range.end;\n }\n}\n\nmodule.exports = GrammarLocation;\n","// @ts-check\n\"use strict\";\n\nconst GrammarLocation = require(\"./grammar-location\");\n\n// Thrown when the grammar contains an error.\n/** @type {import(\"./peg\").GrammarError} */\nclass GrammarError extends SyntaxError {\n /**\n *\n * @param {string} message\n * @param {PEG.LocationRange} [location]\n * @param {PEG.DiagnosticNote[]} [diagnostics]\n */\n constructor(message, location, diagnostics) {\n super(message);\n this.name = \"GrammarError\";\n this.location = location;\n if (diagnostics === undefined) {\n diagnostics = [];\n }\n this.diagnostics = diagnostics;\n // All problems if this error is thrown by the plugin and not at stage\n // checking phase\n this.stage = null;\n this.problems = [\n /** @type {PEG.Problem} */\n ([\"error\", message, location, diagnostics]),\n ];\n }\n\n toString() {\n let str = super.toString();\n if (this.location) {\n str += \"\\n at \";\n if ((this.location.source !== undefined)\n && (this.location.source !== null)) {\n str += `${this.location.source}:`;\n }\n str += `${this.location.start.line}:${this.location.start.column}`;\n }\n for (const diag of this.diagnostics) {\n str += \"\\n from \";\n if ((diag.location.source !== undefined)\n && (diag.location.source !== null)) {\n str += `${diag.location.source}:`;\n }\n str += `${diag.location.start.line}:${diag.location.start.column}: ${diag.message}`;\n }\n\n return str;\n }\n\n /**\n * Format the error with associated sources. The `location.source` should have\n * a `toString()` representation in order the result to look nice. If source\n * is `null` or `undefined`, it is skipped from the output\n *\n * Sample output:\n * ```\n * Error: Label \"head\" is already defined\n * --> examples/arithmetics.pegjs:15:17\n * |\n * 15 | = head:Factor head:(_ (\"*\" / \"/\") _ Factor)* {\n * | ^^^^\n * note: Original label location\n * --> examples/arithmetics.pegjs:15:5\n * |\n * 15 | = head:Factor head:(_ (\"*\" / \"/\") _ Factor)* {\n * | ^^^^\n * ```\n *\n * @param {import(\"./peg\").SourceText[]} sources mapping from location source to source text\n *\n * @returns {string} the formatted error\n */\n format(sources) {\n const srcLines = sources.map(({ source, text }) => ({\n source,\n text: (text !== null && text !== undefined)\n ? String(text).split(/\\r\\n|\\n|\\r/g)\n : [],\n }));\n\n /**\n * Returns a highlighted piece of source to which the `location` points\n *\n * @param {import(\"./peg\").LocationRange} location\n * @param {number} indent How much width in symbols line number strip should have\n * @param {string} message Additional message that will be shown after location\n * @returns {string}\n */\n function entry(location, indent, message = \"\") {\n let str = \"\";\n const src = srcLines.find(({ source }) => source === location.source);\n const s = location.start;\n const offset_s = GrammarLocation.offsetStart(location);\n if (src) {\n const e = location.end;\n const line = src.text[s.line - 1];\n const last = s.line === e.line ? e.column : line.length + 1;\n const hatLen = (last - s.column) || 1;\n if (message) {\n str += `\\nnote: ${message}`;\n }\n str += `\n --> ${location.source}:${offset_s.line}:${offset_s.column}\n${\"\".padEnd(indent)} |\n${offset_s.line.toString().padStart(indent)} | ${line}\n${\"\".padEnd(indent)} | ${\"\".padEnd(s.column - 1)}${\"\".padEnd(hatLen, \"^\")}`;\n } else {\n str += `\\n at ${location.source}:${offset_s.line}:${offset_s.column}`;\n if (message) {\n str += `: ${message}`;\n }\n }\n\n return str;\n }\n\n /**\n * Returns a formatted representation of the one problem in the error.\n *\n * @param {import(\"./peg\").Severity} severity Importance of the message\n * @param {string} message Test message of the problem\n * @param {import(\"./peg\").LocationRange} [location] Location of the problem in the source\n * @param {import(\"./peg\").DiagnosticNote[]} [diagnostics] Additional notes about the problem\n * @returns {string}\n */\n function formatProblem(severity, message, location, diagnostics = []) {\n // Calculate maximum width of all lines\n // eslint-disable-next-line no-useless-assignment\n let maxLine = -Infinity;\n if (location) {\n maxLine = diagnostics.reduce(\n (t, { location }) => Math.max(\n t, GrammarLocation.offsetStart(location).line\n ),\n location.start.line\n );\n } else {\n maxLine = Math.max.apply(\n null,\n diagnostics.map(d => d.location.start.line)\n );\n }\n maxLine = maxLine.toString().length;\n\n let str = `${severity}: ${message}`;\n if (location) {\n str += entry(location, maxLine);\n }\n for (const diag of diagnostics) {\n str += entry(diag.location, maxLine, diag.message);\n }\n\n return str;\n }\n\n // \"info\" problems are only appropriate if in verbose mode.\n // Handle them separately.\n return this.problems\n .filter(p => p[0] !== \"info\")\n .map(p => formatProblem(...p)).join(\"\\n\\n\");\n }\n}\n\nmodule.exports = GrammarError;\n","\"use strict\";\n\n// Simple AST node visitor builder.\nconst visitor = {\n build(functions) {\n function visit(node, ...args) {\n return functions[node.type](node, ...args);\n }\n\n function visitNop() {\n // Do nothing.\n }\n\n function visitExpression(node, ...args) {\n return visit(node.expression, ...args);\n }\n\n function visitChildren(property) {\n return function(node, ...args) {\n // We do not use .map() here, because if you need the result\n // of applying visitor to children you probable also need to\n // process it in some way, therefore you anyway have to override\n // this method. If you do not needed that, we do not waste time\n // and memory for creating the output array\n node[property].forEach(child => visit(child, ...args));\n };\n }\n\n const DEFAULT_FUNCTIONS = {\n grammar(node, ...args) {\n for (const imp of node.imports) {\n visit(imp, ...args);\n }\n\n if (node.topLevelInitializer) {\n if (Array.isArray(node.topLevelInitializer)) {\n for (const tli of node.topLevelInitializer) {\n visit(tli, ...args);\n }\n } else {\n visit(node.topLevelInitializer, ...args);\n }\n }\n\n if (node.initializer) {\n if (Array.isArray(node.initializer)) {\n for (const init of node.initializer) {\n visit(init, ...args);\n }\n } else {\n visit(node.initializer, ...args);\n }\n }\n\n node.rules.forEach(rule => visit(rule, ...args));\n },\n\n grammar_import: visitNop,\n top_level_initializer: visitNop,\n initializer: visitNop,\n rule: visitExpression,\n named: visitExpression,\n choice: visitChildren(\"alternatives\"),\n action: visitExpression,\n sequence: visitChildren(\"elements\"),\n labeled: visitExpression,\n text: visitExpression,\n simple_and: visitExpression,\n simple_not: visitExpression,\n optional: visitExpression,\n zero_or_more: visitExpression,\n one_or_more: visitExpression,\n repeated(node, ...args) {\n if (node.delimiter) {\n visit(node.delimiter, ...args);\n }\n\n return visit(node.expression, ...args);\n },\n group: visitExpression,\n semantic_and: visitNop,\n semantic_not: visitNop,\n rule_ref: visitNop,\n library_ref: visitNop,\n literal: visitNop,\n class: visitNop,\n any: visitNop,\n };\n\n Object.keys(DEFAULT_FUNCTIONS).forEach(type => {\n if (!Object.prototype.hasOwnProperty.call(functions, type)) {\n functions[type] = DEFAULT_FUNCTIONS[type];\n }\n });\n\n return visit;\n },\n};\n\nmodule.exports = visitor;\n","\"use strict\";\n\nconst visitor = require(\"./visitor\");\n\n/**\n * Combine two things, each of which might be an array, into a single value,\n * in the order [...a, ...b].\n *\n * @template T\n * @param {T | T[]} a\n * @param {T | T[]} b\n * @returns {T | T[]}\n */\nfunction combinePossibleArrays(a, b) {\n // First might be an array, second will not. Either might be null.\n if (!(a && b)) {\n return a || b;\n }\n const aa = Array.isArray(a) ? a : [a];\n aa.push(b);\n return aa;\n}\n\n// AST utilities.\nconst asts = {\n /**\n * Find the rule with the given name, if it exists.\n *\n * @param {PEG.ast.Grammar} ast\n * @param {string} name\n * @returns {PEG.ast.Rule | undefined}\n */\n findRule(ast, name) {\n for (let i = 0; i < ast.rules.length; i++) {\n if (ast.rules[i].name === name) {\n return ast.rules[i];\n }\n }\n\n return undefined;\n },\n\n /**\n * Find the index of the rule with the given name, if it exists.\n * Otherwise returns -1.\n *\n * @param {PEG.ast.Grammar} ast\n * @param {string} name\n * @returns {number}\n */\n indexOfRule(ast, name) {\n for (let i = 0; i < ast.rules.length; i++) {\n if (ast.rules[i].name === name) {\n return i;\n }\n }\n\n // istanbul ignore next Presence of rules checked using another approach that not involve this function\n // Any time when it is called the rules always exist\n return -1;\n },\n\n alwaysConsumesOnSuccess(ast, node) {\n function consumesTrue() { return true; }\n function consumesFalse() { return false; }\n\n const consumes = visitor.build({\n choice(node) {\n return node.alternatives.every(consumes);\n },\n\n sequence(node) {\n return node.elements.some(consumes);\n },\n\n simple_and: consumesFalse,\n simple_not: consumesFalse,\n optional: consumesFalse,\n zero_or_more: consumesFalse,\n repeated(node) {\n // If minimum is `null` it is equals to maximum (parsed from `|exact|` syntax)\n const min = node.min ? node.min : node.max;\n\n // If the low boundary is variable then it can be zero.\n // Expression, repeated zero times, does not consume any input\n // but always matched - so it does not always consumes on success\n if (min.type !== \"constant\" || min.value === 0) {\n return false;\n }\n if (consumes(node.expression)) {\n return true;\n }\n // |node.delimiter| used only when |node.expression| match at least two times\n // The first `if` filtered out all non-constant minimums, so at this point\n // |min.value| is always a constant\n if (min.value > 1 && node.delimiter && consumes(node.delimiter)) {\n return true;\n }\n\n return false;\n },\n semantic_and: consumesFalse,\n semantic_not: consumesFalse,\n\n rule_ref(node) {\n const rule = asts.findRule(ast, node.name);\n\n // Because we run all checks in one stage, some rules could be missing.\n // Checking for missing rules could be executed in parallel to this check\n return rule ? consumes(rule) : undefined;\n },\n\n // No way to know for external rules.\n library_ref: consumesFalse,\n\n literal(node) {\n return node.value !== \"\";\n },\n\n class: consumesTrue,\n any: consumesTrue,\n });\n\n return consumes(node);\n },\n\n combine(asts) {\n return asts.reduce((combined, ast) => {\n combined.topLevelInitializer = combinePossibleArrays(\n combined.topLevelInitializer,\n ast.topLevelInitializer\n );\n combined.initializer = combinePossibleArrays(\n combined.initializer,\n ast.initializer\n );\n combined.rules = combined.rules.concat(ast.rules);\n return combined;\n });\n },\n};\n\nmodule.exports = asts;\n","// @ts-check\n\"use strict\";\n\n/**\n * Generate trampoline stubs for each rule imported into this namespace.\n *\n * @example\n * import bar from \"./lib.js\" // Default rule imported into this namespace\n * import {baz} from \"./lib.js\" // One rule imported into this namespace by name\n *\n * @type {PEG.Pass}\n */\nfunction addImportedRules(ast) {\n let libraryNumber = 0;\n for (const imp of ast.imports) {\n for (const what of imp.what) {\n let original = undefined;\n switch (what.type) {\n case \"import_binding_all\":\n // Don't create stub.\n continue;\n case \"import_binding_default\":\n // Use the default (usually first) rule.\n break;\n case \"import_binding\":\n original = what.binding;\n break;\n case \"import_binding_rename\":\n original = what.rename;\n break;\n default:\n throw new TypeError(\"Unknown binding type\");\n }\n ast.rules.push({\n type: \"rule\",\n name: what.binding,\n nameLocation: what.location,\n expression: {\n type: \"library_ref\",\n name: original,\n library: imp.from.module,\n libraryNumber,\n location: what.location,\n },\n location: imp.from.location,\n });\n }\n libraryNumber++;\n }\n}\n\nmodule.exports = addImportedRules;\n","// @ts-check\n\"use strict\";\n\nconst visitor = require(\"../visitor\");\n\n/**\n * @param {PEG.ast.Grammar} ast\n * @param {string} name\n * @returns {number}\n */\nfunction findLibraryNumber(ast, name) {\n let libraryNumber = 0;\n for (const imp of ast.imports) {\n for (const what of imp.what) {\n if ((what.type === \"import_binding_all\") && (what.binding === name)) {\n return libraryNumber;\n }\n }\n libraryNumber++;\n }\n\n return -1;\n}\n\n/** @type {PEG.Pass} */\nfunction fixLibraryNumbers(ast, _options, session) {\n const check = visitor.build({\n library_ref(/** @type {PEG.ast.LibraryReference} */ node) {\n if (node.libraryNumber === -1) {\n node.libraryNumber = findLibraryNumber(ast, node.library);\n if (node.libraryNumber === -1) {\n session.error(\n `Unknown module \"${node.library}\"`,\n node.location\n );\n }\n }\n },\n });\n check(ast);\n}\n\nmodule.exports = fixLibraryNumbers;\n","\"use strict\";\n\n// Bytecode instruction opcodes.\nconst opcodes = {\n // Stack Manipulation\n\n /** @deprecated Unused */\n PUSH: 0, // PUSH c\n PUSH_EMPTY_STRING: 35, // PUSH_EMPTY_STRING\n PUSH_UNDEFINED: 1, // PUSH_UNDEFINED\n PUSH_NULL: 2, // PUSH_NULL\n PUSH_FAILED: 3, // PUSH_FAILED\n PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY\n PUSH_CURR_POS: 5, // PUSH_CURR_POS\n POP: 6, // POP\n POP_CURR_POS: 7, // POP_CURR_POS\n POP_N: 8, // POP_N n\n NIP: 9, // NIP\n APPEND: 10, // APPEND\n WRAP: 11, // WRAP n\n TEXT: 12, // TEXT\n PLUCK: 36, // PLUCK n, k, p1, ..., pK\n\n // Conditions and Loops\n\n IF: 13, // IF t, f\n IF_ERROR: 14, // IF_ERROR t, f\n IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f\n IF_LT: 30, // IF_LT min, t, f\n IF_GE: 31, // IF_GE max, t, f\n IF_LT_DYNAMIC: 32, // IF_LT_DYNAMIC min, t, f\n IF_GE_DYNAMIC: 33, // IF_GE_DYNAMIC max, t, f\n WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b\n\n // Matching\n\n MATCH_ANY: 17, // MATCH_ANY a, f, ...\n MATCH_STRING: 18, // MATCH_STRING s, a, f, ...\n MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...\n MATCH_CHAR_CLASS: 20, // MATCH_CHAR_CLASS c, a, f, ...\n MATCH_UNICODE_CLASS: 42, // MATCH_UNICODE_CLASS c, a, f, ...\n /** @deprecated Replaced with `MATCH_CHAR_CLASS` */\n MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...\n ACCEPT_N: 21, // ACCEPT_N n\n ACCEPT_STRING: 22, // ACCEPT_STRING s\n FAIL: 23, // FAIL e\n\n // Calls\n\n LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p\n UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS\n CALL: 26, // CALL f, n, pc, p1, p2, ..., pN\n\n // Rules\n\n RULE: 27, // RULE r\n LIBRARY_RULE: 41, // LIBRARY_RULE moduleIndex, whatIndex\n\n // Failure Reporting\n\n SILENT_FAILS_ON: 28, // SILENT_FAILS_ON\n SILENT_FAILS_OFF: 29, // SILENT_FAILS_OFF\n\n // Because the tests have hard-coded opcode numbers, don't renumber\n // existing opcodes. New opcodes that have been put in the correct\n // sections above are repeated here in order to ensure we don't\n // reuse them.\n //\n // IF_LT: 30\n // IF_GE: 31\n // IF_LT_DYNAMIC: 32\n // IF_GE_DYNAMIC: 33\n // 34 reserved for @mingun\n // PUSH_EMPTY_STRING: 35\n // PLUCK: 36\n\n SOURCE_MAP_PUSH: 37, // SOURCE_MAP_PUSH loc-index\n SOURCE_MAP_POP: 38, // SOURCE_MAP_POP\n SOURCE_MAP_LABEL_PUSH: 39, // SOURCE_MAP_LABEL_PUSH sp, literal-index, loc-index\n SOURCE_MAP_LABEL_POP: 40, // SOURCE_MAP_LABEL_POP sp\n // LIBRARY_RULE: 41,\n // MATCH_UNICODE_CLASS: 42,\n};\n\nmodule.exports = opcodes;\n","// @ts-check\n\"use strict\";\n\n/**\n * Intern strings or objects, so there is only one copy of each, by value.\n * Objects may need to be converted to another representation before storing.\n * Each inputs corresponds to a number, starting with 0.\n *\n * @template [T=string],[V=T]\n */\nclass Intern {\n /**\n * @typedef {object} InternOptions\n * @property {(input: V) => string} [stringify=String] Represent the\n * converted input as a string, for value comparison.\n * @property {(input: T) => V} [convert=(x) => x] Convert the input to its\n * stored form. Required if type V is not the same as type T. Return\n * falsy value to have this input not be added; add() will return -1 in\n * this case.\n */\n\n /**\n * @param {InternOptions} [options]\n */\n constructor(options) {\n /** @type {Required<InternOptions>} */\n this.options = {\n stringify: String,\n convert: x => /** @type {V} */ (/** @type {unknown} */ (x)),\n ...options,\n };\n /** @type {V[]} */\n this.items = [];\n /** @type {Record<string, number>} */\n this.offsets = Object.create(null);\n }\n\n /**\n * Intern an item, getting it's asssociated number. Returns -1 for falsy\n * inputs. O(1) with constants tied to the convert and stringify options.\n *\n * @param {T} input\n * @return {number}\n */\n add(input) {\n const c = this.options.convert(input);\n if (!c) {\n return -1;\n }\n const s = this.options.stringify(c);\n let num = this.offsets[s];\n if (num === undefined) {\n num = this.items.push(c) - 1;\n this.offsets[s] = num;\n }\n return num;\n }\n\n /**\n * @param {number} i\n * @returns {V}\n */\n get(i) {\n return this.items[i];\n }\n\n /**\n * @template U\n * @param {(value: V, index: number, array: V[]) => U} fn\n * @returns {U[]}\n */\n map(fn) {\n return this.items.map(fn);\n }\n}\n\nmodule.exports = Intern;\n","\"use strict\";\n\nconst visitor = require(\"../visitor\");\nconst asts = require(\"../asts\");\nconst GrammarError = require(\"../../grammar-error\");\n\nconst ALWAYS_MATCH = 1;\nconst SOMETIMES_MATCH = 0;\nconst NEVER_MATCH = -1;\n\n// Inference match result of the each node. Can be:\n// -1: negative result, matching of that node always fails\n// 0: neutral result, may be fail, may be match\n// 1: positive result, always match\nfunction inferenceMatchResult(ast) {\n function sometimesMatch(node) { return (node.match = SOMETIMES_MATCH); }\n function alwaysMatch(node) {\n // eslint-disable-next-line no-use-before-define -- Mutual recursion\n inference(node.expression);\n\n return (node.match = ALWAYS_MATCH);\n }\n\n function inferenceExpression(node) {\n // eslint-disable-next-line no-use-before-define -- Mutual recursion\n return (node.match = inference(node.expression));\n }\n function inferenceElements(elements, forChoice) {\n const length = elements.length;\n let always = 0;\n let never = 0;\n\n for (let i = 0; i < length; ++i) {\n // eslint-disable-next-line no-use-before-define -- Mutual recursion\n const result = inference(elements[i]);\n\n if (result === ALWAYS_MATCH) { ++always; }\n if (result === NEVER_MATCH) { ++never; }\n }\n\n if (always === length) {\n return ALWAYS_MATCH;\n }\n if (forChoice) {\n return never === length ? NEVER_MATCH : SOMETIMES_MATCH;\n }\n\n return never > 0 ? NEVER_MATCH : SOMETIMES_MATCH;\n }\n\n const inference = visitor.build({\n rule(node) {\n // eslint-disable-next-line init-declarations\n let oldResult;\n let count = 0;\n\n // If property not yet calculated, do that\n if (typeof node.match === \"undefined\") {\n node.match = SOMETIMES_MATCH;\n do {\n oldResult = node.match;\n node.match = inference(node.expression);\n // 6 == 3! -- permutations count for all transitions from one match\n // state to another.\n // After 6 iterations the cycle with guarantee begins\n // For example, an input of `start = [] start` will generate the\n // sequence: 0 -> -1 -> -1 (then stop)\n //\n // A more complex grammar theoretically would generate the\n // sequence: 0 -> 1 -> 0 -> -1 -> 0 -> 1 -> ... (then cycle)\n // but there are no examples of such grammars yet (possible, they\n // do not exist at all)\n\n // istanbul ignore next This is canary test, shouldn't trigger in real life\n if (++count > 6) {\n throw new GrammarError(\n \"Infinity cycle detected when trying to evaluate node match result\",\n node.location\n );\n }\n } while (oldResult !== node.match);\n }\n\n return node.match;\n },\n named: inferenceExpression,\n choice(node) {\n return (node.match = inferenceElements(node.alternatives, true));\n },\n action: inferenceExpression,\n sequence(node) {\n return (node.match = inferenceElements(node.elements, false));\n },\n labeled: inferenceExpression,\n text: inferenceExpression,\n simple_and: inferenceExpression,\n simple_not(node) {\n return (node.match = -inference(node.expression));\n },\n optional: alwaysMatch,\n zero_or_more: alwaysMatch,\n one_or_more: inferenceExpression,\n repeated(node) {\n const match = inference(node.expression);\n const dMatch = node.delimiter ? inference(node.delimiter) : NEVER_MATCH;\n // If minimum is `null` it is equals to maximum (parsed from `|exact|` syntax)\n const min = node.min ? node.min : node.max;\n\n // If any boundary are variable - it can be negative, and it that case\n // node does not match, but it may be match with some other values\n if (min.type !== \"constant\" || node.max.type !== \"constant\") {\n return (node.match = SOMETIMES_MATCH);\n }\n // Now both boundaries is constants\n // If the upper boundary is zero or minimum exceeds maximum,\n // matching is impossible\n if (node.max.value === 0\n || (node.max.value !== null && min.value > node.max.value)\n ) {\n return (node.match = NEVER_MATCH);\n }\n\n if (match === NEVER_MATCH) {\n // If an expression always fails, a range will also always fail\n // (with the one exception - never matched expression repeated\n // zero times always match and returns an empty array).\n return (node.match = min.value === 0 ? ALWAYS_MATCH : NEVER_MATCH);\n }\n if (match === ALWAYS_MATCH) {\n if (node.delimiter && min.value >= 2) {\n // If an expression always match the final result determined only\n // by the delimiter, but delimiter used only when count of elements\n // two and more\n return (node.match = dMatch);\n }\n\n return (node.match = ALWAYS_MATCH);\n }\n\n // Here `match === SOMETIMES_MATCH`\n if (node.delimiter && min.value >= 2) {\n // If an expression always match the final result determined only\n // by the delimiter, but delimiter used only when count of elements\n // two and more\n return (\n // If a delimiter never match then the range also never match (because\n // there at least one delimiter)\n node.match = dMatch === NEVER_MATCH ? NEVER_MATCH : SOMETIMES_MATCH\n );\n }\n\n return (node.match = min.value === 0 ? ALWAYS_MATCH : SOMETIMES_MATCH);\n },\n group: inferenceExpression,\n semantic_and: sometimesMatch,\n semantic_not: sometimesMatch,\n rule_ref(node) {\n const rule = asts.findRule(ast, node.name);\n if (!rule) {\n return SOMETIMES_MATCH;\n }\n return (node.match = inference(rule));\n },\n library_ref() {\n // Can't look into pre-compiled rules.\n return 0;\n },\n literal(node) {\n // Empty literal always match on any input\n const match = node.value.length === 0 ? ALWAYS_MATCH : SOMETIMES_MATCH;\n\n return (node.match = match);\n },\n class(node) {\n // Empty character class never match on any input\n const match = node.parts.length === 0 ? NEVER_MATCH : SOMETIMES_MATCH;\n\n return (node.match = match);\n },\n // |any| not match on empty input\n any: sometimesMatch,\n });\n\n inference(ast);\n}\n\ninferenceMatchResult.ALWAYS_MATCH = ALWAYS_MATCH;\ninferenceMatchResult.SOMETIMES_MATCH = SOMETIMES_MATCH;\ninferenceMatchResult.NEVER_MATCH = NEVER_MATCH;\n\nmodule.exports = inferenceMatchResult;\n","// @ts-check\n\"use strict\";\n\nconst asts = require(\"../asts\");\nconst op = require(\"../opcodes\");\nconst visitor = require(\"../visitor\");\nconst Intern = require(\"../intern\");\nconst { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require(\"./inference-match-result\");\n\n/**\n * @typedef {import(\"../../peg\")} PEG\n */\n\n// Generates bytecode.\n//\n// Instructions\n// ============\n//\n// Stack Manipulation\n// ------------------\n//\n// [35] PUSH_EMPTY_STRING\n//\n// stack.push(\"\");\n//\n// [1] PUSH_UNDEFINED\n//\n// stack.push(undefined);\n//\n// [2] PUSH_NULL\n//\n// stack.push(null);\n//\n// [3] PUSH_FAILED\n//\n// stack.push(FAILED);\n//\n// [4] PUSH_EMPTY_ARRAY\n//\n// stack.push([]);\n//\n// [5] PUSH_CURR_POS\n//\n// stack.push(currPos);\n//\n// [6] POP\n//\n// stack.pop();\n//\n// [7] POP_CURR_POS\n//\n// currPos = stack.pop();\n//\n// [8] POP_N n\n//\n// stack.pop(n);\n//\n// [9] NIP\n//\n// value = stack.pop();\n// stack.pop();\n// stack.push(value);\n//\n// [10] APPEND\n//\n// value = stack.pop();\n// array = stack.pop();\n// array.push(value);\n// stack.push(array);\n//\n// [11] WRAP n\n//\n// stack.push(stack.pop(n));\n//\n// [12] TEXT\n//\n// stack.push(input.substring(stack.pop(), currPos));\n//\n// [36] PLUCK n, k, p1, ..., pK\n//\n// value = [stack[p1], ..., stack[pK]]; // when k != 1\n// -or-\n// value = stack[p1]; // when k == 1\n//\n// stack.pop(n);\n// stack.push(value);\n//\n// Conditions and Loops\n// --------------------\n//\n// [13] IF t, f\n//\n// if (stack.top()) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [14] IF_ERROR t, f\n//\n// if (stack.top() === FAILED) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [15] IF_NOT_ERROR t, f\n//\n// if (stack.top() !== FAILED) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [30] IF_LT min, t, f\n//\n// if (stack.top().length < min) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [31] IF_GE max, t, f\n//\n// if (stack.top().length >= max) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [32] IF_LT_DYNAMIC min, t, f\n//\n// if (stack.top().length < stack[min]) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [33] IF_GE_DYNAMIC max, t, f\n//\n// if (stack.top().length >= stack[max]) {\n// interpret(ip + 3, ip + 3 + t);\n// } else {\n// interpret(ip + 3 + t, ip + 3 + t + f);\n// }\n//\n// [16] WHILE_NOT_ERROR b\n//\n// while(stack.top() !== FAILED) {\n// interpret(ip + 2, ip + 2 + b);\n// }\n//\n// Matching\n// --------\n//\n// [17] MATCH_ANY a, f, ...\n//\n// if (input.length > currPos) {\n// interpret(ip + 3, ip + 3 + a);\n// } else {\n// interpret(ip + 3 + a, ip + 3 + a + f);\n// }\n//\n// [18] MATCH_STRING s, a, f, ...\n//\n// if (input.substr(currPos, literals[s].length) === literals[s]) {\n// interpret(ip + 4, ip + 4 + a);\n// } else {\n// interpret(ip + 4 + a, ip + 4 + a + f);\n// }\n//\n// [19] MATCH_STRING_IC s, a, f, ...\n//\n// if (input.substr(currPos, literals[s].length).toLowerCase() === literals[s]) {\n// interpret(ip + 4, ip + 4 + a);\n// } else {\n// interpret(ip + 4 + a, ip + 4 + a + f);\n// }\n//\n// [20] MATCH_CHAR_CLASS c, a, f, ...\n//\n// if (classes[c].test(input.charAt(currPos))) {\n// interpret(ip + 4, ip + 4 + a);\n// } else {\n// interpret(ip + 4 + a, ip + 4 + a + f);\n// }\n//\n// [42] MATCH_UNICODE_CLASS c, a, f, ...\n//\n// if (classes[c].test(input.unicodeCharAt(currPos))) {\n// interpret(ip + 4, ip + 4 + a);\n// } else {\n// interpret(ip + 4 + a, ip + 4 + a + f);\n// }\n//\n// [21] ACCEPT_N n\n//\n// stack.push(input.substring(currPos, n));\n// currPos += n;\n//\n// [22] ACCEPT_STRING s\n//\n// stack.push(literals[s]);\n// currPos += literals[s].length;\n//\n// [23] FAIL e\n//\n// stack.push(FAILED);\n// fail(expectations[e]);\n//\n// Calls\n// -----\n//\n// [24] LOAD_SAVED_POS p\n//\n// savedPos = stack[p];\n//\n// [25] UPDATE_SAVED_POS\n//\n// savedPos = currPos;\n//\n// [26] CALL f, n, pc, p1, p2, ..., pN\n//\n// value = functions[f](stack[p1], ..., stack[pN]);\n// stack.pop(n);\n// stack.push(value);\n//\n// Rules\n// -----\n//\n// [27] RULE r\n//\n// stack.push(parseRule(r));\n//\n// [41] LIBRARY_RULE moduleIndex whatIndex\n//\n// stack.push(callLibrary(module, what));\n//\n// Failure Reporting\n// -----------------\n//\n// [28] SILENT_FAILS_ON\n//\n// silentFails++;\n//\n// [29] SILENT_FAILS_OFF\n//\n// silentFails--;\n//\n// Source Mapping\n// --------------\n//\n// [37] SOURCE_MAP_PUSH n\n//\n// Everything generated from here until the corresponding SOURCE_MAP_POP\n// will be wrapped in a SourceNode tagged with locations[n].\n//\n// [38] SOURCE_MAP_POP\n//\n// See above.\n//\n// [39] SOURCE_MAP_LABEL_PUSH sp, label, loc\n//\n// Mark that the stack location sp will be used to hold the value\n// of the label named literals[label], with location info locations[loc]\n//\n// [40] SOURCE_MAP_LABEL_POP sp\n//\n// End the region started by [39]\n//\n// This pass can use the results of other previous passes, each of which can\n// change the AST (and, as consequence, the bytecode).\n//\n// In particular, if the pass |inferenceMatchResult| has been run before this pass,\n// then each AST node will contain a |match| property, which represents a possible\n// match result of the node:\n// - `<0` - node is never matched, for example, `!('a'*)` (negation of the always\n// matched node). Generator can put |FAILED| to the stack immediately\n// - `=0` - sometimes node matched, sometimes not. This is the same behavior\n// when |match| is missed\n// - `>0` - node is always matched, for example, `'a'*` (because result is an\n// empty array, or an array with some elements). The generator does not\n// need to add a check for |FAILED|, because it is impossible\n//\n// To handle the situation, when the |inferenceMatchResult| has not run (that\n// happens, for example, in tests), the |match| value extracted using the\n// `|0` trick, which performing cast of any value to an integer with value `0`\n// that is equivalent of an unknown match result and signals the generator that\n// runtime check for the |FAILED| is required. Trick is explained on the\n// Wikipedia page (https://en.wikipedia.org/wiki/Asm.js#Code_generation)\n/**\n *\n * @param {PEG.ast.Grammar} ast\n * @param {PEG.ParserOptions} options\n */\nfunction generateBytecode(ast, options) {\n const literals = new Intern();\n /** @type Intern<PEG.ast.CharacterClass, PEG.ast.GrammarCharacterClass> */\n const classes = new Intern({\n stringify: JSON.stringify,\n /** @type {(input: PEG.ast.CharacterClass) => PEG.ast.GrammarCharacterClass} */\n convert: node => ({\n value: node.parts,\n inverted: node.inverted,\n ignoreCase: node.ignoreCase,\n unicode: node.unicode,\n }),\n });\n /** @type Intern<PEG.ast.GrammarExpectation> */\n const expectations = new Intern({\n stringify: JSON.stringify,\n });\n /**\n * @type {Intern<string | undefined, string>}\n */\n const importedNames = new Intern();\n /** @type PEG.ast.FunctionConst[] */\n const functions = [];\n /** @type PEG.LocationRange[] */\n const locations = [];\n\n /**\n * @param {boolean} predicate\n * @param {string[]} params\n * @param {{code:string; codeLocation: PEG.LocationRange}} node\n * @returns {number}\n */\n function addFunctionConst(predicate, params, node) {\n const func = {\n predicate,\n params,\n body: node.code,\n location: node.codeLocation,\n };\n const pattern = JSON.stringify(func);\n const index = functions.findIndex(f => JSON.stringify(f) === pattern);\n\n return index === -1 ? functions.push(func) - 1 : index;\n }\n\n /**\n * @param {PEG.LocationRange} location\n * @returns {number}\n */\n function addLocation(location) {\n // Don't bother de-duplicating. There can be a lot of locations,\n // they will almost never collide, and unlike the \"consts\" above,\n // it won't affect code generation even if they do.\n return locations.push(location) - 1;\n }\n\n /** @typedef {Record<string, number>} Env */\n /** @typedef {{ sp: number; env:Env; action:PEG.ast.Action|null; pluck?: number[] }} Context */\n\n /**\n * @param {Env} env\n * @returns {Env}\n */\n function cloneEnv(env) {\n /** @type {Env} */\n const clone = {};\n\n Object.keys(env).forEach(name => {\n clone[name] = env[name];\n });\n\n return clone;\n }\n\n /**\n * @param {number[]} first\n * @param {number[][]} args\n * @returns {number[]}\n */\n function buildSequence(first, ...args) {\n return first.concat(...args);\n }\n\n /**\n * @param {number} match\n * @param {number[]} condCode\n * @param {number[]} thenCode\n * @param {number[]} elseCode\n * @returns {number[]}\n */\n function buildCondition(match, condCode, thenCode, elseCode) {\n if (match === ALWAYS_MATCH) { return thenCode; }\n if (match === NEVER_MATCH) { return elseCode; }\n\n return condCode.concat(\n [thenCode.length, elseCode.length],\n thenCode,\n elseCode\n );\n }\n\n /**\n * @param {number[]} condCode\n * @param {number[]} bodyCode\n * @returns {number[]}\n */\n function buildLoop(condCode, bodyCode) {\n return condCode.concat([bodyCode.length], bodyCode);\n }\n\n /**\n * @param {number} functionIndex\n * @param {number} delta\n * @param {Env} env\n * @param {number} sp\n * @returns {number[]}\n */\n function buildCall(functionIndex, delta, env, sp) {\n const params = Object.keys(env).map(name => sp - env[name]);\n\n return [op.CALL, functionIndex, delta, params.length].concat(params);\n }\n\n /**\n * @template T\n * @param {PEG.ast.Expr<T>} expression\n * @param {boolean} negative\n * @param {Context} context\n * @returns {number[]}\n */\n function buildSimplePredicate(expression, negative, context) {\n const match = expression.match || 0;\n\n return buildSequence(\n [op.PUSH_CURR_POS],\n [op.SILENT_FAILS_ON],\n // eslint-disable-next-line no-use-before-define -- Mutual recursion\n generate(expression, {\n sp: context.sp + 1,\n env: cloneEnv(context.env),\n action: null,\n }),\n [op.SILENT_FAILS_OFF],\n buildCondition(\n negative ? -match : match,\n [negative ? op.IF_ERROR : op.IF_NOT_ERROR],\n buildSequence(\n [op.POP],\n [negative ? op.POP : op.POP_CURR_POS],\n [op.PUSH_UNDEFINED]\n ),\n buildSequence(\n [op.POP],\n [negative ? op.POP_CURR_POS : op.POP],\n [op.PUSH_FAILED]\n )\n )\n );\n }\n /**\n *\n * @param {PEG.ast.SemanticPredicate} node\n * @param {boolean} negative\n * @param {Context} context\n * @returns {number[]}\n */\n function buildSemanticPredicate(node, negative, context) {\n const functionIndex = addFunctionConst(\n true, Object.keys(context.env), node\n );\n\n return buildSequence(\n [op.UPDATE_SAVED_POS],\n buildCall(functionIndex, 0, context.env, context.sp),\n buildCondition(\n node.match || 0,\n [op.IF],\n buildSequence(\n [op.POP],\n negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]\n ),\n buildSequence(\n [op.POP],\n negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]\n )\n )\n );\n }\n\n /**\n * @param {number[]} expressionCode\n * @returns {number[]}\n */\n function buildAppendLoop(expressionCode) {\n return buildLoop(\n [op.WHILE_NOT_ERROR],\n buildSequence([op.APPEND], expressionCode)\n );\n }\n\n /**\n * @param {never} boundary\n * @returns {Error}\n */\n function unknownBoundary(boundary) {\n const b = /** @type {{ type: string }} */(boundary);\n return new Error(`Unknown boundary type \"${b.type}\" for the \"repeated\" node`);\n }\n\n /**\n *\n * @param {import(\"../../peg\").ast.RepeatedBoundary} boundary\n * @param {{ [label: string]: number}} env Mapping of label names to stack positions\n * @param {number} sp Number of the first free slot in the stack\n * @param {number} offset\n *\n * @returns {{ pre: number[], post: number[], sp: number}}\n * Bytecode that should be added before and after parsing and new\n * first free slot in the stack\n */\n function buildRangeCall(boundary, env, sp, offset) {\n switch (boundary.type) {\n case \"constant\":\n return { pre: [], post: [], sp };\n case \"variable\":\n boundary.sp = offset + sp - env[boundary.value];\n return { pre: [], post: [], sp };\n case \"function\": {\n boundary.sp = offset;\n\n const functionIndex = addFunctionConst(\n true,\n Object.keys(env),\n { code: boundary.value, codeLocation: boundary.codeLocation }\n );\n\n return {\n pre: buildCall(functionIndex, 0, env, sp),\n post: [op.NIP],\n // +1 for the function result\n sp: sp + 1,\n };\n }\n\n // istanbul ignore next Because we never generate invalid boundary type we cannot reach this branch\n default:\n throw unknownBoundary(boundary);\n }\n }\n\n /**\n * @param {number[]} expressionCode Bytecode for parsing repetitions\n * @param {import(\"../../peg\").ast.RepeatedBoundary} max Maximum boundary of repetitions.\n * If `null`, the maximum boundary is unlimited\n *\n * @returns {number[]} Bytecode that performs check of the maximum boundary\n */\n function buildCheckMax(expressionCode, max) {\n if (max.value !== null) {\n const checkCode = max.type === \"constant\"\n ? [op.IF_GE, max.value]\n : [op.IF_GE_DYNAMIC, max.sp || 0];\n\n // Push `peg$FAILED` - this break loop on next iteration, so |result|\n // will contains not more then |max| elements.\n return buildCondition(\n SOMETIMES_MATCH,\n checkCode, // if (r.length >= max) stack:[ [elem...] ]\n [op.PUSH_FAILED], // elem = peg$FAILED; stack:[ [elem...], peg$FAILED ]\n expressionCode // else\n ); // elem = expr(); stack:[ [elem...], elem ]\n }\n\n return expressionCode;\n }\n\n /* eslint capitalized-comments: \"off\" */\n /**\n * @param {number[]} expressionCode Bytecode for parsing repeated elements\n * @param {import(\"../../peg\").ast.RepeatedBoundary} min Minimum boundary of repetitions.\n * If `null`, the minimum boundary is zero\n *\n * @returns {number[]} Bytecode that performs check of the minimum boundary\n */\n function buildCheckMin(expressionCode, min) {\n const checkCode = min.type === \"constant\"\n ? [op.IF_LT, min.value]\n : [op.IF_LT_DYNAMIC, min.sp || 0];\n\n return buildSequence(\n expressionCode, // result = [elem...]; stack:[ pos, [elem...] ]\n buildCondition(\n SOMETIMES_MATCH,\n checkCode, // if (result.length < min) {\n /* eslint-disable @stylistic/indent -- Clarity */\n [op.POP, op.POP_CURR_POS, // currPos = savedPos; stack:[ ]\n op.PUSH_FAILED], // result = peg$FAILED; stack:[ peg$FAILED ]\n /* eslint-enable @stylistic/indent */\n [op.NIP] // } stack:[ [elem...] ]\n )\n );\n }\n /**\n *\n * @param {PEG.ast.Expression|null} delimiterNode\n * @param {number} expressionMatch\n * @param {number[]} expressionCode\n * @param {Context} context\n * @param {number} offset\n * @returns {number[]}\n */\n function buildRangeBody(\n delimiterNode,\n expressionMatch,\n expressionCode,\n context,\n offset\n ) {\n if (delimiterNode) {\n return buildSequence( // stack:[ ]\n [op.PUSH_CURR_POS], // pos = peg$currPos; stack:[ pos ]\n // eslint-disable-next-line no-use-before-define -- Mutual recursion\n generate(delimiterNode, { // item = delim(); stack:[ pos, delim ]\n // +1 for the saved offset\n sp: context.sp + offset + 1,\n env: cloneEnv(context.env),\n action: null,\n }),\n buildCondition(\n delimiterNode.match || 0,\n [op.IF_NOT_ERROR], // if (item !== peg$FAILED) {\n buildSequence(\n [op.POP], // stack:[ pos ]\n expressionCode, // item = expr(); stack:[ pos, item ]\n buildCondition(\n -expressionMatch,\n [op.IF_ERROR], // if (item === peg$FAILED) {\n // If element FAILED, rollback currPos to saved value.\n /* eslint-disable @stylistic/indent -- Clarity */\n [op.POP, // stack:[ pos ]\n op.POP_CURR_POS, // peg$currPos = pos; stack:[ ]\n op.PUSH_FAILED], // item = peg$FAILED; stack:[ peg$FAILED ]\n /* eslint-enable @stylistic/indent */\n // Else, just drop saved currPos.\n [op.NIP] // } stack:[ item ]\n )\n ), // }\n // If delimiter FAILED, currPos not changed, so just drop it.\n [op.NIP] // stack:[ peg$FAILED ]\n