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
JavaScript
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,