Initial
This commit is contained in:
190
resources/app/node_modules/peggy/lib/compiler/passes/inference-match-result.js
generated
vendored
Normal file
190
resources/app/node_modules/peggy/lib/compiler/passes/inference-match-result.js
generated
vendored
Normal 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;
|
||||
Reference in New Issue
Block a user