This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

145
resources/app/node_modules/peggy/lib/compiler/asts.js generated vendored Normal file
View File

@@ -0,0 +1,145 @@
"use strict";
const visitor = require("./visitor");
/**
* Combine two things, each of which might be an array, into a single value,
* in the order [...a, ...b].
*
* @template T
* @param {T | T[]} a
* @param {T | T[]} b
* @returns {T | T[]}
*/
function combinePossibleArrays(a, b) {
// First might be an array, second will not. Either might be null.
if (!(a && b)) {
return a || b;
}
const aa = Array.isArray(a) ? a : [a];
aa.push(b);
return aa;
}
// AST utilities.
const 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(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return ast.rules[i];
}
}
return undefined;
},
/**
* 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(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return i;
}
}
// istanbul ignore next Presence of rules checked using another approach that not involve this function
// Any time when it is called the rules always exist
return -1;
},
alwaysConsumesOnSuccess(ast, node) {
function consumesTrue() { return true; }
function consumesFalse() { return false; }
const consumes = visitor.build({
choice(node) {
return node.alternatives.every(consumes);
},
sequence(node) {
return node.elements.some(consumes);
},
simple_and: consumesFalse,
simple_not: consumesFalse,
optional: consumesFalse,
zero_or_more: consumesFalse,
repeated(node) {
// If minimum is `null` it is equals to maximum (parsed from `|exact|` syntax)
const min = node.min ? node.min : node.max;
// If the low boundary is variable then it can be zero.
// Expression, repeated zero times, does not consume any input
// but always matched - so it does not always consumes on success
if (min.type !== "constant" || min.value === 0) {
return false;
}
if (consumes(node.expression)) {
return true;
}
// |node.delimiter| used only when |node.expression| match at least two times
// The first `if` filtered out all non-constant minimums, so at this point
// |min.value| is always a constant
if (min.value > 1 && node.delimiter && consumes(node.delimiter)) {
return true;
}
return false;
},
semantic_and: consumesFalse,
semantic_not: consumesFalse,
rule_ref(node) {
const rule = asts.findRule(ast, node.name);
// Because we run all checks in one stage, some rules could be missing.
// Checking for missing rules could be executed in parallel to this check
return rule ? consumes(rule) : undefined;
},
library_ref() {
// No way to know for external rules.
return false;
},
literal(node) {
return node.value !== "";
},
class: consumesTrue,
any: consumesTrue,
});
return consumes(node);
},
combine(asts) {
return asts.reduce((combined, ast) => {
combined.topLevelInitializer = combinePossibleArrays(
combined.topLevelInitializer,
ast.topLevelInitializer
);
combined.initializer = combinePossibleArrays(
combined.initializer,
ast.initializer
);
combined.rules = combined.rules.concat(ast.rules);
return combined;
});
},
};
module.exports = asts;

170
resources/app/node_modules/peggy/lib/compiler/index.js generated vendored Normal file
View File

@@ -0,0 +1,170 @@
"use strict";
const addImportedRules = require("./passes/add-imported-rules");
const fixLibraryNumbers = require("./passes/fix-library-numbers");
const generateBytecode = require("./passes/generate-bytecode");
const generateJS = require("./passes/generate-js");
const inferenceMatchResult = require("./passes/inference-match-result");
const removeProxyRules = require("./passes/remove-proxy-rules");
const mergeCharacterClasses = require("./passes/merge-character-classes");
const reportDuplicateImports = require("./passes/report-duplicate-imports");
const reportDuplicateLabels = require("./passes/report-duplicate-labels");
const reportDuplicateRules = require("./passes/report-duplicate-rules");
const reportInfiniteRecursion = require("./passes/report-infinite-recursion");
const reportInfiniteRepetition = require("./passes/report-infinite-repetition");
const reportUndefinedRules = require("./passes/report-undefined-rules");
const reportIncorrectPlucking = require("./passes/report-incorrect-plucking");
const Session = require("./session");
const visitor = require("./visitor");
const { base64 } = require("./utils");
function processOptions(options, defaults) {
const processedOptions = {};
Object.keys(options).forEach(name => {
processedOptions[name] = options[name];
});
Object.keys(defaults).forEach(name => {
if (!Object.prototype.hasOwnProperty.call(processedOptions, name)) {
processedOptions[name] = defaults[name];
}
});
return processedOptions;
}
function isSourceMapCapable(target) {
if (typeof target === "string") {
return target.length > 0;
}
return target && (typeof target.offset === "function");
}
const compiler = {
// AST node visitor builder. Useful mainly for plugins which manipulate the
// AST.
visitor,
// Compiler passes.
//
// Each pass is a function that is passed the AST. It can perform checks on it
// or modify it as needed. If the pass encounters a semantic error, it throws
// |peg.GrammarError|.
passes: {
prepare: [
addImportedRules,
reportInfiniteRecursion,
],
check: [
reportUndefinedRules,
reportDuplicateRules,
reportDuplicateLabels,
reportInfiniteRepetition,
reportIncorrectPlucking,
reportDuplicateImports,
],
transform: [
fixLibraryNumbers,
removeProxyRules,
mergeCharacterClasses,
inferenceMatchResult,
],
generate: [
generateBytecode,
generateJS,
],
},
// Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
// if the AST contains a semantic error. Note that not all errors are detected
// during the generation and some may protrude to the generated parser and
// cause its malfunction.
compile(ast, passes, options) {
options = options !== undefined ? options : {};
options = processOptions(options, {
allowedStartRules: [ast.rules[0].name],
cache: false,
dependencies: {},
exportVar: null,
format: "bare",
output: "parser",
trace: false,
});
if (!Array.isArray(options.allowedStartRules)) {
throw new Error("allowedStartRules must be an array");
}
if (options.allowedStartRules.length === 0) {
throw new Error("Must have at least one start rule");
}
const allRules = ast.rules.map(r => r.name);
// "*" means all rules are start rules. "*" is not a valid rule name.
if (options.allowedStartRules.some(r => r === "*")) {
options.allowedStartRules = allRules;
} else {
for (const rule of options.allowedStartRules) {
if (allRules.indexOf(rule) === -1) {
throw new Error(`Unknown start rule "${rule}"`);
}
}
}
// Due to https://github.com/mozilla/source-map/issues/444
// grammarSource is required
if (((options.output === "source-and-map")
|| (options.output === "source-with-inline-map"))
&& !isSourceMapCapable(options.grammarSource)) {
throw new Error("Must provide grammarSource (as a string or GrammarLocation) in order to generate source maps");
}
const session = new Session(options);
Object.keys(passes).forEach(stage => {
session.stage = stage;
session.info(`Process stage ${stage}`);
passes[stage].forEach(pass => {
session.info(`Process pass ${stage}.${pass.name}`);
pass(ast, options, session);
});
// Collect all errors by stage
session.checkErrors();
});
switch (options.output) {
case "parser":
// eslint-disable-next-line no-eval -- Required
return eval(ast.code.toString());
case "source":
return ast.code.toString();
case "source-and-map":
return ast.code;
case "source-with-inline-map": {
if (typeof TextEncoder === "undefined") {
throw new Error("TextEncoder is not supported by this platform");
}
const sourceMap = ast.code.toStringWithSourceMap();
const encoder = new TextEncoder();
const b64 = base64(
encoder.encode(JSON.stringify(sourceMap.map.toJSON()))
);
return sourceMap.code + `\
//\x23 sourceMappingURL=data:application/json;charset=utf-8;base64,${b64}
`;
}
case "ast":
return ast;
default:
throw new Error("Invalid output format: " + options.output + ".");
}
},
};
module.exports = compiler;

View File

@@ -0,0 +1,77 @@
// @ts-check
"use strict";
/**
* Intern strings or objects, so there is only one copy of each, by value.
* Objects may need to be converted to another representation before storing.
* Each inputs corresponds to a number, starting with 0.
*
* @template [T=string],[V=T]
*/
class Intern {
/**
* @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(options) {
/** @type {Required<InternOptions>} */
this.options = {
stringify: String,
convert: x => /** @type {V} */ (/** @type {unknown} */ (x)),
...options,
};
/** @type {V[]} */
this.items = [];
/** @type {Record<string, number>} */
this.offsets = {};
}
/**
* 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 === undefined) {
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);
}
}
module.exports = Intern;

View File

@@ -0,0 +1,83 @@
"use strict";
// Bytecode instruction opcodes.
const opcodes = {
// Stack Manipulation
/** @deprecated Unused */
PUSH: 0, // PUSH c
PUSH_EMPTY_STRING: 35, // PUSH_EMPTY_STRING
PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
PUSH_NULL: 2, // PUSH_NULL
PUSH_FAILED: 3, // PUSH_FAILED
PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
PUSH_CURR_POS: 5, // PUSH_CURR_POS
POP: 6, // POP
POP_CURR_POS: 7, // POP_CURR_POS
POP_N: 8, // POP_N n
NIP: 9, // NIP
APPEND: 10, // APPEND
WRAP: 11, // WRAP n
TEXT: 12, // TEXT
PLUCK: 36, // PLUCK n, k, p1, ..., pK
// Conditions and Loops
IF: 13, // IF t, f
IF_ERROR: 14, // IF_ERROR t, f
IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
IF_LT: 30, // IF_LT min, t, f
IF_GE: 31, // IF_GE max, t, f
IF_LT_DYNAMIC: 32, // IF_LT_DYNAMIC min, t, f
IF_GE_DYNAMIC: 33, // IF_GE_DYNAMIC max, t, f
WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
// Matching
MATCH_ANY: 17, // MATCH_ANY a, f, ...
MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
MATCH_CHAR_CLASS: 20, // MATCH_CHAR_CLASS c, a, f, ...
/** @deprecated Replaced with `MATCH_CHAR_CLASS` */
MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
ACCEPT_N: 21, // ACCEPT_N n
ACCEPT_STRING: 22, // ACCEPT_STRING s
FAIL: 23, // FAIL e
// Calls
LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
// Rules
RULE: 27, // RULE r
LIBRARY_RULE: 41, // LIBRARY_RULE moduleIndex, whatIndex
// Failure Reporting
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
SILENT_FAILS_OFF: 29, // SILENT_FAILS_OFF
// 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_PUSH loc-index
SOURCE_MAP_POP: 38, // SOURCE_MAP_POP
SOURCE_MAP_LABEL_PUSH: 39, // SOURCE_MAP_LABEL_PUSH sp, literal-index, loc-index
SOURCE_MAP_LABEL_POP: 40, // SOURCE_MAP_LABEL_POP sp
// LIBRARY_RULE: 41,
};
module.exports = opcodes;

View File

@@ -0,0 +1,52 @@
// @ts-check
"use strict";
/**
* Generate trampoline stubs for each rule imported into this namespace.
*
* @example
* import bar from "./lib.js" // Default rule imported into this namespace
* import {baz} from "./lib.js" // One rule imported into this namespace by name
*
* @type {PEG.Pass}
*/
function addImportedRules(ast) {
let libraryNumber = 0;
for (const imp of ast.imports) {
for (const what of imp.what) {
let original = undefined;
switch (what.type) {
case "import_binding_all":
// Don't create stub.
continue;
case "import_binding_default":
// Use the default (usually first) rule.
break;
case "import_binding":
original = what.binding;
break;
case "import_binding_rename":
original = what.rename;
break;
default:
throw new TypeError("Unknown binding type");
}
ast.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++;
}
}
module.exports = addImportedRules;

View File

@@ -0,0 +1,43 @@
// @ts-check
"use strict";
const visitor = require("../visitor");
/**
* @param {PEG.ast.Grammar} ast
* @param {string} name
* @returns {number}
*/
function findLibraryNumber(ast, name) {
let libraryNumber = 0;
for (const imp of ast.imports) {
for (const what of imp.what) {
if ((what.type === "import_binding_all") && (what.binding === name)) {
return libraryNumber;
}
}
libraryNumber++;
}
return -1;
}
/** @type {PEG.Pass} */
function fixLibraryNumbers(ast, _options, session) {
const check = visitor.build({
library_ref(/** @type {PEG.ast.LibraryReference} */ node) {
if (node.libraryNumber === -1) {
node.libraryNumber = findLibraryNumber(ast, node.library);
if (node.libraryNumber === -1) {
session.error(
`Unknown module "${node.library}"`,
node.location
);
}
}
},
});
check(ast);
}
module.exports = fixLibraryNumbers;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
"use strict";
const visitor = require("../visitor");
const asts = require("../asts");
const GrammarError = require("../../grammar-error");
const ALWAYS_MATCH = 1;
const SOMETIMES_MATCH = 0;
const NEVER_MATCH = -1;
// Inference match result of the each node. Can be:
// -1: negative result, matching of that node always fails
// 0: neutral result, may be fail, may be match
// 1: positive result, always match
function inferenceMatchResult(ast) {
function sometimesMatch(node) { return (node.match = SOMETIMES_MATCH); }
function alwaysMatch(node) {
// eslint-disable-next-line no-use-before-define -- Mutual recursion
inference(node.expression);
return (node.match = ALWAYS_MATCH);
}
function inferenceExpression(node) {
// eslint-disable-next-line no-use-before-define -- Mutual recursion
return (node.match = inference(node.expression));
}
function inferenceElements(elements, forChoice) {
const length = elements.length;
let always = 0;
let never = 0;
for (let i = 0; i < length; ++i) {
// eslint-disable-next-line no-use-before-define -- Mutual recursion
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;
}
const inference = visitor.build({
rule(node) {
let oldResult = undefined;
let count = 0;
// If property not yet calculated, do that
if (typeof node.match === "undefined") {
node.match = SOMETIMES_MATCH;
do {
oldResult = node.match;
node.match = inference(node.expression);
// 6 == 3! -- permutations count for all transitions from one match
// state to another.
// After 6 iterations the cycle with guarantee begins
// For example, an input of `start = [] start` will generate the
// sequence: 0 -> -1 -> -1 (then stop)
//
// A more complex grammar theoretically would generate the
// sequence: 0 -> 1 -> 0 -> -1 -> 0 -> 1 -> ... (then cycle)
// but there are no examples of such grammars yet (possible, they
// do not exist at all)
// istanbul ignore next This is canary test, shouldn't trigger in real life
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;
// If minimum is `null` it is equals to maximum (parsed from `|exact|` syntax)
const min = node.min ? node.min : node.max;
// If any boundary are variable - it can be negative, and it that case
// node does not match, but it may be match with some other values
if (min.type !== "constant" || node.max.type !== "constant") {
return (node.match = SOMETIMES_MATCH);
}
// Now both boundaries is constants
// If the upper boundary is zero or minimum exceeds maximum,
// matching is impossible
if (node.max.value === 0
|| (node.max.value !== null && min.value > node.max.value)
) {
return (node.match = NEVER_MATCH);
}
if (match === NEVER_MATCH) {
// If an expression always fails, a range will also always fail
// (with the one exception - never matched expression repeated
// zero times always match and returns an empty array).
return (node.match = min.value === 0 ? ALWAYS_MATCH : NEVER_MATCH);
}
if (match === ALWAYS_MATCH) {
if (node.delimiter && min.value >= 2) {
// If an expression always match the final result determined only
// by the delimiter, but delimiter used only when count of elements
// two and more
return (node.match = dMatch);
}
return (node.match = ALWAYS_MATCH);
}
// Here `match === SOMETIMES_MATCH`
if (node.delimiter && min.value >= 2) {
// If an expression always match the final result determined only
// by the delimiter, but delimiter used only when count of elements
// two and more
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(ast, node.name);
if (!rule) {
return SOMETIMES_MATCH;
}
return (node.match = inference(rule));
},
library_ref() {
// Can't look into pre-compiled rules.
return 0;
},
literal(node) {
// Empty literal always match on any input
const match = node.value.length === 0 ? ALWAYS_MATCH : SOMETIMES_MATCH;
return (node.match = match);
},
class(node) {
// Empty character class never match on any input
const match = node.parts.length === 0 ? NEVER_MATCH : SOMETIMES_MATCH;
return (node.match = match);
},
// |any| not match on empty input
any: sometimesMatch,
});
inference(ast);
}
inferenceMatchResult.ALWAYS_MATCH = ALWAYS_MATCH;
inferenceMatchResult.SOMETIMES_MATCH = SOMETIMES_MATCH;
inferenceMatchResult.NEVER_MATCH = NEVER_MATCH;
module.exports = inferenceMatchResult;

View File

@@ -0,0 +1,191 @@
// @ts-check
"use strict";
/**
* @typedef {import("../../peg")} PEG
*/
/** @type {PEG.compiler.visitor} */
const visitor = require("../visitor");
/**
* @param {unknown} target
* @param {unknown} source
*/
function cloneOver(target, source) {
const t = /** @type {Record<string,unknown>} */ (target);
const s = /** @type {Record<string,unknown>} */ (source);
Object.keys(t).forEach(key => delete t[key]);
Object.keys(s).forEach(key => { t[key] = s[key]; });
}
/**
* Clean up the parts array of a `class` node, by sorting,
* then removing "contained" ranges, and merging overlapping
* or adjacent ranges.
*
* @param {PEG.ast.CharacterClass["parts"]} parts
*/
function cleanParts(parts) {
// Sort parts on increasing start, and then decreasing end.
parts.sort((a, b) => {
const [aStart, aEnd] = Array.isArray(a) ? a : [a, a];
const [bStart, bEnd] = Array.isArray(b) ? b : [b, b];
if (aStart !== bStart) {
return aStart < bStart ? -1 : 1;
}
if (aEnd !== bEnd) {
return aEnd > bEnd ? -1 : 1;
}
return 0;
});
let prevStart = "";
let prevEnd = "";
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const [curStart, curEnd] = Array.isArray(part) ? part : [part, part];
if (curEnd <= prevEnd) {
// Current range is contained in previous range,
// so drop it.
parts.splice(i--, 1);
continue;
}
if (prevEnd.charCodeAt(0) + 1 >= curStart.charCodeAt(0)) {
// Current and previous ranges overlap, or are adjacent.
// Drop the current, and extend the previous range.
parts.splice(i--, 1);
parts[i] = [prevStart, prevEnd = curEnd];
continue;
}
prevStart = curStart;
prevEnd = curEnd;
}
return parts;
}
/**
* Merges a choice character classes into a character class
* @param {PEG.ast.Grammar} ast
*/
function mergeCharacterClasses(ast) {
// Build a map from rule names to rules for quick lookup of
// ref_rules.
const rules = Object.create(null);
ast.rules.forEach(rule => (rules[rule.name] = rule.expression));
// Keep a map of which rules have been processed, so that when
// we find a ref_rule, we can make sure its processed, before we
// try to use it.
const processedRules = Object.create(null);
const [asClass, merge] = [
/**
* Determine whether a node can be represented as a simple character class,
* and return that class if so.
*
* @param {PEG.ast.Expression} node - the node to inspect
* @param {boolean} [clone] - if true, always return a new node that
* can be modified by the caller
* @returns {PEG.ast.CharacterClass | null}
*/
(node, clone) => {
if (node.type === "class" && !node.inverted) {
if (clone) {
node = { ...node };
node.parts = [...node.parts];
}
return node;
}
if (node.type === "literal" && node.value.length === 1) {
return {
type: "class",
parts: [node.value],
inverted: false,
ignoreCase: node.ignoreCase,
location: node.location,
};
}
if (node.type === "rule_ref") {
const ref = rules[node.name];
if (ref) {
if (!processedRules[node.name]) {
processedRules[node.name] = true;
merge(ref);
}
const cls = asClass(ref, true);
if (cls) {
cls.location = node.location;
}
return cls;
}
}
return null;
},
visitor.build({
choice(node) {
/** @type {PEG.ast.CharacterClass | null} */
let prev = null;
let changed = false;
node.alternatives.forEach((alt, i) => {
merge(alt);
const cls = asClass(alt);
if (!cls) {
prev = null;
return;
}
if (prev && prev.ignoreCase === cls.ignoreCase) {
prev.parts.push(...cls.parts);
node.alternatives[i - 1] = prev;
node.alternatives[i] = prev;
prev.location = {
source: prev.location.source,
start: prev.location.start,
end: cls.location.end,
};
changed = true;
} else {
prev = cls;
}
});
if (changed) {
node.alternatives = node.alternatives.filter(
(alt, i, arr) => !i || alt !== arr[i - 1]
);
node.alternatives.forEach((alt, i) => {
if (alt.type === "class") {
alt.parts = cleanParts(alt.parts);
if (alt.parts.length === 1
&& !Array.isArray(alt.parts[0])
&& !alt.inverted) {
node.alternatives[i] = {
type: "literal",
value: alt.parts[0],
ignoreCase: alt.ignoreCase,
location: alt.location,
};
}
}
});
if (node.alternatives.length === 1) {
cloneOver(node, node.alternatives[0]);
}
}
},
text(node) {
merge(node.expression);
if (node.expression.type === "class"
|| node.expression.type === "literal") {
const location = node.location;
cloneOver(node, node.expression);
node.location = location;
}
},
}),
];
ast.rules.forEach(rule => {
processedRules[rule.name] = true;
merge(rule.expression);
});
}
module.exports = mergeCharacterClasses;

View File

@@ -0,0 +1,49 @@
"use strict";
const asts = require("../asts");
const visitor = require("../visitor");
// Removes proxy rules -- that is, rules that only delegate to other rule.
function removeProxyRules(ast, options, session) {
function isProxyRule(node) {
return node.type === "rule" && node.expression.type === "rule_ref";
}
function replaceRuleRefs(ast, from, to) {
const replace = visitor.build({
rule_ref(node) {
if (node.name === from) {
node.name = to;
session.info(
`Proxy rule "${from}" replaced by the rule "${to}"`,
node.location,
[{
message: "This rule will be used",
location: asts.findRule(ast, to).nameLocation,
}]
);
}
},
});
replace(ast);
}
const indices = [];
ast.rules.forEach((rule, i) => {
if (isProxyRule(rule)) {
replaceRuleRefs(ast, rule.name, rule.expression.name);
if (options.allowedStartRules.indexOf(rule.name) === -1) {
indices.push(i);
}
}
});
indices.reverse();
indices.forEach(i => { ast.rules.splice(i, 1); });
}
module.exports = removeProxyRules;

View File

@@ -0,0 +1,28 @@
// @ts-check
"use strict";
/** @type {PEG.Pass} */
function reportDuplicateImports(ast, _options, session) {
/** @type {Record<string, PEG.LocationRange>} */
const all = {};
for (const imp of ast.imports) {
for (const what of imp.what) {
if (what.type === "import_binding_all") {
if (Object.prototype.hasOwnProperty.call(all, what.binding)) {
session.error(
`Module "${what.binding}" is already imported`,
what.location,
[{
message: "Original module location",
location: all[what.binding],
}]
);
}
all[what.binding] = what.location;
}
}
}
}
module.exports = reportDuplicateImports;

View File

@@ -0,0 +1,72 @@
"use strict";
const visitor = require("../visitor");
// Checks that each label is defined only once within each scope.
function reportDuplicateLabels(ast, options, session) {
function cloneEnv(env) {
const clone = {};
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
return clone;
}
function checkExpressionWithClonedEnv(node, env) {
// eslint-disable-next-line no-use-before-define -- Mutual recursion
check(node.expression, cloneEnv(env));
}
const check = visitor.build({
rule(node) {
check(node.expression, { });
},
choice(node, env) {
node.alternatives.forEach(alternative => {
check(alternative, cloneEnv(env));
});
},
action: checkExpressionWithClonedEnv,
labeled(node, env) {
const label = node.label;
if (label && Object.prototype.hasOwnProperty.call(env, label)) {
session.error(
`Label "${node.label}" is already defined`,
node.labelLocation,
[{
message: "Original label location",
location: env[label],
}]
);
}
check(node.expression, env);
env[node.label] = node.labelLocation;
},
text: checkExpressionWithClonedEnv,
simple_and: checkExpressionWithClonedEnv,
simple_not: checkExpressionWithClonedEnv,
optional: checkExpressionWithClonedEnv,
zero_or_more: checkExpressionWithClonedEnv,
one_or_more: checkExpressionWithClonedEnv,
repeated(node, env) {
if (node.delimiter) {
check(node.delimiter, cloneEnv(env));
}
check(node.expression, cloneEnv(env));
},
group: checkExpressionWithClonedEnv,
});
check(ast);
}
module.exports = reportDuplicateLabels;

View File

@@ -0,0 +1,32 @@
"use strict";
const visitor = require("../visitor");
// Checks that each rule is defined only once.
function reportDuplicateRules(ast, options, session) {
const rules = {};
const check = visitor.build({
rule(node) {
if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
session.error(
`Rule "${node.name}" is already defined`,
node.nameLocation,
[{
message: "Original rule location",
location: rules[node.name],
}]
);
// Do not rewrite original rule location
return;
}
rules[node.name] = node.nameLocation;
},
});
check(ast);
}
module.exports = reportDuplicateRules;

View File

@@ -0,0 +1,37 @@
"use strict";
const visitor = require("../visitor");
//
// Compiler pass to ensure the following are enforced:
//
// - plucking can not be done with an action block
//
function reportIncorrectPlucking(ast, options, session) {
const check = visitor.build({
action(node) {
check(node.expression, node);
},
labeled(node, action) {
if (node.pick) {
if (action) {
session.error(
"\"@\" cannot be used with an action block",
node.labelLocation,
[{
message: "Action block location",
location: action.codeLocation,
}]
);
}
}
check(node.expression);
},
});
check(ast);
}
module.exports = reportIncorrectPlucking;

View File

@@ -0,0 +1,101 @@
"use strict";
const asts = require("../asts");
const visitor = require("../visitor");
// Reports left recursion in the grammar, which prevents infinite recursion in
// the generated parser.
//
// Both direct and indirect recursion is detected. The pass also correctly
// reports cases like this:
//
// start = "a"? start
//
// In general, if a rule reference can be reached without consuming any input,
// it can lead to left recursion.
function reportInfiniteRecursion(ast, options, session) {
// Array with rule names for error message
const visitedRules = [];
// Array with rule_refs for diagnostic
const backtraceRefs = [];
const check = visitor.build({
rule(node) {
if (session.errors > 0) {
return;
}
visitedRules.push(node.name);
check(node.expression);
visitedRules.pop();
},
sequence(node) {
if (session.errors > 0) {
return;
}
node.elements.every(element => {
check(element);
if (session.errors > 0) {
return false;
}
return !asts.alwaysConsumesOnSuccess(ast, element);
});
},
repeated(node) {
if (session.errors > 0) {
return;
}
check(node.expression);
// If an expression does not consume input then recursion
// over delimiter is possible
if (node.delimiter
&& !asts.alwaysConsumesOnSuccess(ast, node.expression)
) {
check(node.delimiter);
}
},
rule_ref(node) {
if (session.errors > 0) {
return;
}
backtraceRefs.push(node);
const rule = asts.findRule(ast, node.name);
if (visitedRules.indexOf(node.name) !== -1) {
visitedRules.push(node.name);
session.error(
"Possible infinite loop when parsing (left recursion: "
+ visitedRules.join(" -> ")
+ ")",
rule.nameLocation,
backtraceRefs.map((ref, i, a) => ({
message: i + 1 !== a.length
? `Step ${i + 1}: call of the rule "${ref.name}" without input consumption`
: `Step ${i + 1}: call itself without input consumption - left recursion`,
location: ref.location,
}))
);
// Because we enter into recursion we should break it
return;
}
// Because we run all checks in one stage, some rules could be missing - this check
// executed in parallel
if (rule) {
check(rule);
}
backtraceRefs.pop();
},
});
check(ast);
}
module.exports = reportInfiniteRecursion;

View File

@@ -0,0 +1,64 @@
"use strict";
const asts = require("../asts");
const visitor = require("../visitor");
// Reports expressions that don't consume any input inside |*|, |+| or repeated in the
// grammar, which prevents infinite loops in the generated parser.
function reportInfiniteRepetition(ast, options, session) {
const check = visitor.build({
zero_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
session.error(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input)",
node.location
);
}
},
one_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
session.error(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input)",
node.location
);
}
},
repeated(node) {
// No need to check min or max. They can only be numbers, variable
// names, or code blocks.
if (node.delimiter) {
check(node.delimiter);
}
if (asts.alwaysConsumesOnSuccess(ast, node.expression)
|| (node.delimiter
&& asts.alwaysConsumesOnSuccess(ast, node.delimiter))) {
return;
}
if (node.max.value === null) {
session.error(
"Possible infinite loop when parsing (unbounded range repetition used with an expression that may not consume any input)",
node.location
);
} else {
// If minimum is `null` it is equals to maximum (parsed from `|exact|` syntax)
const min = node.min ? node.min : node.max;
// Because the high boundary is defined, infinity repetition is not possible
// but the grammar will waste of CPU
session.warning(
min.type === "constant" && node.max.type === "constant"
? `An expression may not consume any input and may always match ${node.max.value} times`
: "An expression may not consume any input and may always match with a maximum repetition count",
node.location
);
}
},
});
check(ast);
}
module.exports = reportInfiniteRepetition;

View File

@@ -0,0 +1,22 @@
"use strict";
const asts = require("../asts");
const visitor = require("../visitor");
// Checks that all referenced rules exist.
function reportUndefinedRules(ast, options, session) {
const check = visitor.build({
rule_ref(node) {
if (!asts.findRule(ast, node.name)) {
session.error(
`Rule "${node.name}" is not defined`,
node.location
);
}
},
});
check(ast);
}
module.exports = reportUndefinedRules;

View File

@@ -0,0 +1,84 @@
"use strict";
const GrammarError = require("../grammar-error");
class Defaults {
constructor(options) {
options = typeof options !== "undefined" ? options : {};
if (typeof options.error === "function") { this.error = options.error; }
if (typeof options.warning === "function") { this.warning = options.warning; }
if (typeof options.info === "function") { this.info = options.info; }
}
// eslint-disable-next-line class-methods-use-this -- Abstract
error() {
// Intentionally empty placeholder
}
// eslint-disable-next-line class-methods-use-this -- Abstract
warning() {
// Intentionally empty placeholder
}
// eslint-disable-next-line class-methods-use-this -- Abstract
info() {
// Intentionally empty placeholder
}
}
class Session {
constructor(options) {
this._callbacks = new Defaults(options);
this._firstError = null;
this.errors = 0;
/** @type {import("../peg").Problem[]} */
this.problems = [];
/** @type {import("../peg").Stage} */
this.stage = null;
}
error(...args) {
++this.errors;
// In order to preserve backward compatibility we cannot change `GrammarError`
// constructor, nor throw another class of error:
// - if we change `GrammarError` constructor, this will break plugins that
// throws `GrammarError`
// - if we throw another Error class, this will break parser clients that
// catches GrammarError
//
// So we use a compromise: we throw an `GrammarError` with all found problems
// in the `problems` property, but the thrown error itself is the first
// registered error.
//
// Thus when the old client catches the error it can find all properties on
// the Grammar error that it want. On the other hand the new client can
// inspect the `problems` property to get all problems.
if (this._firstError === null) {
this._firstError = new GrammarError(...args);
this._firstError.stage = this.stage;
this._firstError.problems = this.problems;
}
this.problems.push(["error", ...args]);
this._callbacks.error(this.stage, ...args);
}
warning(...args) {
this.problems.push(["warning", ...args]);
this._callbacks.warning(this.stage, ...args);
}
info(...args) {
this.problems.push(["info", ...args]);
this._callbacks.info(this.stage, ...args);
}
checkErrors() {
if (this.errors !== 0) {
throw this._firstError;
}
}
}
module.exports = Session;

365
resources/app/node_modules/peggy/lib/compiler/stack.js generated vendored Normal file
View File

@@ -0,0 +1,365 @@
// @ts-check
"use strict";
const { SourceNode } = require("source-map-generator");
const GrammarLocation = require("../grammar-location.js");
/**
* @typedef {(string|SourceNode)[]} SourceArray
*/
/** Utility class that helps generating code for C-like languages. */
class Stack {
/**
* Constructs the helper for tracking variable slots of the stack virtual machine
*
* @param {string} ruleName The name of rule that will be used in error messages
* @param {string} varName The prefix for generated names of variables
* @param {string} type The type of the variables. For JavaScript there are `var` or `let`
* @param {number[]} bytecode Bytecode for error messages
*/
constructor(ruleName, varName, type, bytecode) {
/** Last used variable in the stack. */
this.sp = -1;
/** Maximum stack size. */
this.maxSp = -1;
this.varName = varName;
this.ruleName = ruleName;
this.type = type;
this.bytecode = bytecode;
/**
* Map from stack index, to label targetting that index
* @type {Record<number,{label:string,location:import("../peg.js").LocationRange}>}
*/
this.labels = {};
/**
* Stack of in-flight source mappings
* @type {[SourceArray, number, PEG.LocationRange][]}
*/
this.sourceMapStack = [];
}
/**
* Returns name of the variable at the index `i`.
*
* @param {number} i Index for which name must be generated
* @return {string} Generated name
*
* @throws {RangeError} If `i < 0`, which means a stack underflow (there are more `pop`s than `push`es)
*/
name(i) {
if (i < 0) {
throw new RangeError(
`Rule '${this.ruleName}': The variable stack underflow: attempt to use a variable '${this.varName}<x>' at an index ${i}.\nBytecode: ${this.bytecode}`
);
}
return this.varName + i;
}
/**
*
* @param {PEG.LocationRange} location
* @param {SourceArray} chunks
* @param {string} [name]
* @returns
*/
static sourceNode(location, chunks, name) {
const start = GrammarLocation.offsetStart(location);
return new SourceNode(
start.line,
start.column ? start.column - 1 : null,
String(location.source),
chunks,
name
);
}
/**
* Assigns `exprCode` to the new variable in the stack, returns generated code.
* As the result, the size of a stack increases on 1.
*
* @param {string} exprCode Any expression code that must be assigned to the new variable in the stack
* @return {string|SourceNode} Assignment code
*/
push(exprCode) {
if (++this.sp > this.maxSp) { this.maxSp = this.sp; }
const label = this.labels[this.sp];
const code = [this.name(this.sp), " = ", exprCode, ";"];
if (label) {
if (this.sourceMapStack.length) {
const sourceNode = Stack.sourceNode(
label.location,
code.splice(0, 2),
label.label
);
const { parts, location } = this.sourceMapPopInternal();
const newLoc = (location.start.offset < label.location.end.offset)
? {
start: label.location.end,
end: location.end,
source: location.source,
}
: location;
const outerNode = Stack.sourceNode(
newLoc,
code.concat("\n")
);
this.sourceMapStack.push([parts, parts.length + 1, location]);
return new SourceNode(
null,
null,
label.location.source,
[sourceNode, outerNode]
);
} else {
return Stack.sourceNode(
label.location,
code.concat("\n")
);
}
}
return code.join("");
}
/**
* @overload
* @param {undefined} [n]
* @return {string}
*/
/**
* @overload
* @param {number} n
* @return {string[]}
*/
/**
* Returns name or `n` names of the variable(s) from the top of the stack.
*
* @param {number} [n] Quantity of variables, which need to be removed from the stack
* @returns {string[]|string} Generated name(s). If n is defined then it returns an
* array of length `n`
*
* @throws {RangeError} If the stack underflow (there are more `pop`s than `push`es)
*/
pop(n) {
if (n !== undefined) {
this.sp -= n;
return Array.from({ length: n }, (v, i) => this.name(this.sp + 1 + i));
}
return this.name(this.sp--);
}
/**
* Returns name of the first free variable. The same as `index(0)`.
*
* @return {string} Generated name
*
* @throws {RangeError} If the stack is empty (there was no `push`'s yet)
*/
top() { return this.name(this.sp); }
/**
* Returns name of the variable at index `i`.
*
* @param {number} i Index of the variable from top of the stack
* @return {string} Generated name
*
* @throws {RangeError} If `i < 0` or more than the stack size
*/
index(i) {
if (i < 0) {
throw new RangeError(
`Rule '${this.ruleName}': The variable stack overflow: attempt to get a variable at a negative index ${i}.\nBytecode: ${this.bytecode}`
);
}
return this.name(this.sp - i);
}
/**
* Returns variable name that contains result (bottom of the stack).
*
* @return {string} Generated name
*
* @throws {RangeError} If the stack is empty (there was no `push`es yet)
*/
result() {
if (this.maxSp < 0) {
throw new RangeError(
`Rule '${this.ruleName}': The variable stack is empty, can't get the result.\nBytecode: ${this.bytecode}`
);
}
return this.name(0);
}
/**
* Returns defines of all used variables.
*
* @return {string} Generated define variable expression with the type `this.type`.
* If the stack is empty, returns empty string
*/
defines() {
if (this.maxSp < 0) {
return "";
}
return this.type + " " + Array.from({ length: this.maxSp + 1 }, (v, i) => this.name(i)).join(", ") + ";";
}
/**
* Checks that code in the `generateIf` and `generateElse` move the stack pointer in the same way.
*
* @template T
* @param {number} pos Opcode number for error messages
* @param {() => T} generateIf First function that works with this stack
* @param {(() => T)|null} [generateElse] Second function that works with this stack
* @return {T[]}
*
* @throws {Error} If `generateElse` is defined and the stack pointer moved differently in the
* `generateIf` and `generateElse`
*/
checkedIf(pos, generateIf, generateElse) {
const baseSp = this.sp;
const ifResult = generateIf();
if (!generateElse) {
return [ifResult];
}
const thenSp = this.sp;
this.sp = baseSp;
const elseResult = generateElse();
if (thenSp !== this.sp) {
throw new Error(
"Rule '" + this.ruleName + "', position " + pos + ": "
+ "Branches of a condition can't move the stack pointer differently "
+ "(before: " + baseSp + ", after then: " + thenSp + ", after else: " + this.sp + "). "
+ "Bytecode: " + this.bytecode
);
}
return [ifResult, elseResult];
}
/**
* Checks that code in the `generateBody` do not move stack pointer.
*
* @template T
* @param {number} pos Opcode number for error messages
* @param {() => T} generateBody Function that works with this stack
* @return {T}
*
* @throws {Error} If `generateBody` move the stack pointer (if it contains unbalanced `push`es and `pop`s)
*/
checkedLoop(pos, generateBody) {
const baseSp = this.sp;
const result = generateBody();
if (baseSp !== this.sp) {
throw new Error(
"Rule '" + this.ruleName + "', position " + pos + ": "
+ "Body of a loop can't move the stack pointer "
+ "(before: " + baseSp + ", after: " + this.sp + "). "
+ "Bytecode: " + this.bytecode
);
}
return result;
}
/**
*
* @param {SourceArray} parts
* @param {PEG.LocationRange} location
*/
sourceMapPush(parts, location) {
if (this.sourceMapStack.length) {
const top = this.sourceMapStack[this.sourceMapStack.length - 1];
// If the current top of stack starts at the same location as
// the about to be pushed item, we should update its start location to
// be past the new one. Otherwise any code it generates will
// get allocated to the inner node.
if (top[2].start.offset === location.start.offset
&& top[2].end.offset > location.end.offset) {
top[2] = {
start: location.end,
end: top[2].end,
source: top[2].source,
};
}
}
this.sourceMapStack.push([
parts,
parts.length,
location,
]);
}
/**
* @returns {{parts:SourceArray,location:PEG.LocationRange}}
*/
sourceMapPopInternal() {
const elt = this.sourceMapStack.pop();
if (!elt) {
throw new RangeError(
`Rule '${this.ruleName}': Attempting to pop an empty source map stack.\nBytecode: ${this.bytecode}`
);
}
const [
parts,
index,
location,
] = elt;
const chunks = parts.splice(index).map(
chunk => (chunk instanceof SourceNode
? chunk
: chunk + "\n"
)
);
if (chunks.length) {
const start = GrammarLocation.offsetStart(location);
parts.push(new SourceNode(
start.line,
start.column - 1,
String(location.source),
chunks
));
}
return { parts, location };
}
/**
* @param {number} [offset]
* @returns {[SourceArray, number, PEG.LocationRange]|undefined}
*/
sourceMapPop(offset) {
const { location } = this.sourceMapPopInternal();
if (this.sourceMapStack.length
&& location.end.offset
< this.sourceMapStack[this.sourceMapStack.length - 1][2].end.offset) {
const { parts, location: outer } = this.sourceMapPopInternal();
const newLoc = (outer.start.offset < location.end.offset)
? {
start: location.end,
end: outer.end,
source: outer.source,
}
: outer;
this.sourceMapStack.push([
parts,
parts.length + (offset || 0),
newLoc,
]);
}
return undefined;
}
}
module.exports = Stack;

91
resources/app/node_modules/peggy/lib/compiler/utils.js generated vendored Normal file
View File

@@ -0,0 +1,91 @@
"use strict";
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
exports.hex = hex;
function stringEscape(s) {
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
// literal except for the closing quote character, backslash, carriage
// return, line separator, paragraph separator, and line feed. Any character
// may appear in the form of an escape sequence.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // Backslash
.replace(/"/g, "\\\"") // Closing double quote
.replace(/\0/g, "\\0") // Null
.replace(/\x08/g, "\\b") // Backspace
.replace(/\t/g, "\\t") // Horizontal tab
.replace(/\n/g, "\\n") // Line feed
.replace(/\v/g, "\\v") // Vertical tab
.replace(/\f/g, "\\f") // Form feed
.replace(/\r/g, "\\r") // Carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
}
exports.stringEscape = stringEscape;
function regexpClassEscape(s) {
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // Backslash
.replace(/\//g, "\\/") // Closing slash
.replace(/]/g, "\\]") // Closing bracket
.replace(/\^/g, "\\^") // Caret
.replace(/-/g, "\\-") // Dash
.replace(/\0/g, "\\0") // Null
.replace(/\x08/g, "\\b") // Backspace
.replace(/\t/g, "\\t") // Horizontal tab
.replace(/\n/g, "\\n") // Line feed
.replace(/\v/g, "\\v") // Vertical tab
.replace(/\f/g, "\\f") // Form feed
.replace(/\r/g, "\\r") // Carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
}
exports.regexpClassEscape = regexpClassEscape;
/**
* Base64 encode a Uint8Array. Needed for browser compatibility where
* the Buffer class is not available.
*
* @param {Uint8Array} u8 Bytes to encode
* @returns {string} Base64 encoded string
*/
function base64(u8) {
// Note: btoa has the worst API, and even mentioning Buffer here will
// cause rollup to suck it in.
// See RFC4648, sec. 4.
// https://datatracker.ietf.org/doc/html/rfc4648#section-4
const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const rem = u8.length % 3;
const len = u8.length - rem;
let res = "";
for (let i = 0; i < len; i += 3) {
res += A[u8[i] >> 2];
res += A[((u8[i] & 0x3) << 4) | (u8[i + 1] >> 4)];
res += A[((u8[i + 1] & 0xf) << 2) | (u8[i + 2] >> 6)];
res += A[u8[i + 2] & 0x3f];
}
if (rem === 1) {
res += A[u8[len] >> 2];
res += A[(u8[len] & 0x3) << 4];
res += "==";
} else if (rem === 2) {
res += A[u8[len] >> 2];
res += A[((u8[len] & 0x3) << 4) | (u8[len + 1] >> 4)];
res += A[(u8[len + 1] & 0xf) << 2];
res += "=";
}
return res;
}
exports.base64 = base64;

View File

@@ -0,0 +1,100 @@
"use strict";
// Simple AST node visitor builder.
const visitor = {
build(functions) {
function visit(node, ...args) {
return functions[node.type](node, ...args);
}
function visitNop() {
// Do nothing.
}
function visitExpression(node, ...args) {
return visit(node.expression, ...args);
}
function visitChildren(property) {
return function(node, ...args) {
// We do not use .map() here, because if you need the result
// of applying visitor to children you probable also need to
// process it in some way, therefore you anyway have to override
// this method. If you do not needed that, we do not waste time
// and memory for creating the output array
node[property].forEach(child => visit(child, ...args));
};
}
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;
},
};
module.exports = visitor;