完成世界书、骰子、apiconfig页面处理

This commit is contained in:
2026-04-30 01:35:10 +08:00
parent a3e3711b2b
commit ba9b925c32
4602 changed files with 785225 additions and 23 deletions

87
frontend/node_modules/chevrotain/src/api.ts generated vendored Normal file
View File

@@ -0,0 +1,87 @@
/* istanbul ignore file - tricky to import some things from this module during testing */
// semantic version
export { VERSION } from "./version.js";
export {
CstParser,
EmbeddedActionsParser,
ParserDefinitionErrorType,
EMPTY_ALT,
} from "./parse/parser/parser.js";
export { Lexer, LexerDefinitionErrorType } from "./scan/lexer_public.js";
// Tokens utilities
export {
createToken,
createTokenInstance,
EOF,
tokenLabel,
tokenMatcher,
tokenName,
} from "./scan/tokens_public.js";
// Lookahead
export { getLookaheadPaths } from "./parse/grammar/lookahead.js";
export { LLkLookaheadStrategy } from "./parse/grammar/llk_lookahead.js";
// Other Utilities
export { defaultParserErrorProvider } from "./parse/errors_public.js";
export {
EarlyExitException,
isRecognitionException,
MismatchedTokenException,
NotAllInputParsedException,
NoViableAltException,
} from "./parse/exceptions_public.js";
export { defaultLexerErrorProvider } from "./scan/lexer_errors_public.js";
// grammar reflection API
export {
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal,
} from "@chevrotain/gast";
// GAST Utilities
export {
serializeGrammar,
serializeProduction,
GAstVisitor,
} from "@chevrotain/gast";
export { generateCstDts } from "@chevrotain/cst-dts-gen";
/* istanbul ignore next */
export function clearCache() {
console.warn(
"The clearCache function was 'soft' removed from the Chevrotain API." +
"\n\t It performs no action other than printing this message." +
"\n\t Please avoid using it as it will be completely removed in the future",
);
}
export { createSyntaxDiagramsCode } from "./diagrams/render_public.js";
export class Parser {
constructor() {
throw new Error(
"The Parser class has been deprecated, use CstParser or EmbeddedActionsParser instead.\t\n" +
"See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0",
);
}
}

View File

@@ -0,0 +1,2 @@
// TODO: can this be removed? where is it used?
export const IN = "_~IN~_";

View File

@@ -0,0 +1,70 @@
import { flatten, map, uniq } from "lodash-es";
import {
isBranchingProd,
isOptionalProd,
isSequenceProd,
NonTerminal,
Terminal,
} from "@chevrotain/gast";
import { IProduction, TokenType } from "@chevrotain/types";
export function first(prod: IProduction): TokenType[] {
/* istanbul ignore else */
if (prod instanceof NonTerminal) {
// this could in theory cause infinite loops if
// (1) prod A refs prod B.
// (2) prod B refs prod A
// (3) AB can match the empty set
// in other words a cycle where everything is optional so the first will keep
// looking ahead for the next optional part and will never exit
// currently there is no safeguard for this unique edge case because
// (1) not sure a grammar in which this can happen is useful for anything (productive)
return first((<NonTerminal>prod).referencedRule);
} else if (prod instanceof Terminal) {
return firstForTerminal(<Terminal>prod);
} else if (isSequenceProd(prod)) {
return firstForSequence(prod);
} else if (isBranchingProd(prod)) {
return firstForBranching(prod);
} else {
throw Error("non exhaustive match");
}
}
export function firstForSequence(prod: {
definition: IProduction[];
}): TokenType[] {
let firstSet: TokenType[] = [];
const seq = prod.definition;
let nextSubProdIdx = 0;
let hasInnerProdsRemaining = seq.length > nextSubProdIdx;
let currSubProd;
// so we enter the loop at least once (if the definition is not empty
let isLastInnerProdOptional = true;
// scan a sequence until it's end or until we have found a NONE optional production in it
while (hasInnerProdsRemaining && isLastInnerProdOptional) {
currSubProd = seq[nextSubProdIdx];
isLastInnerProdOptional = isOptionalProd(currSubProd);
firstSet = firstSet.concat(first(currSubProd));
nextSubProdIdx = nextSubProdIdx + 1;
hasInnerProdsRemaining = seq.length > nextSubProdIdx;
}
return uniq(firstSet);
}
export function firstForBranching(prod: {
definition: IProduction[];
}): TokenType[] {
const allAlternativesFirsts: TokenType[][] = map(
prod.definition,
(innerProd) => {
return first(innerProd);
},
);
return uniq(flatten<TokenType>(allAlternativesFirsts));
}
export function firstForTerminal(terminal: Terminal): TokenType[] {
return [terminal.terminalType];
}

View File

@@ -0,0 +1,50 @@
import { Rule } from "@chevrotain/gast";
import { defaults, forEach } from "lodash-es";
import { resolveGrammar as orgResolveGrammar } from "../resolver.js";
import { validateGrammar as orgValidateGrammar } from "../checks.js";
import {
defaultGrammarResolverErrorProvider,
defaultGrammarValidatorErrorProvider,
} from "../../errors_public.js";
import { TokenType } from "@chevrotain/types";
import {
IGrammarResolverErrorMessageProvider,
IGrammarValidatorErrorMessageProvider,
IParserDefinitionError,
} from "../types.js";
type ResolveGrammarOpts = {
rules: Rule[];
errMsgProvider?: IGrammarResolverErrorMessageProvider;
};
export function resolveGrammar(
options: ResolveGrammarOpts,
): IParserDefinitionError[] {
const actualOptions: Required<ResolveGrammarOpts> = defaults(options, {
errMsgProvider: defaultGrammarResolverErrorProvider,
});
const topRulesTable: { [ruleName: string]: Rule } = {};
forEach(options.rules, (rule) => {
topRulesTable[rule.name] = rule;
});
return orgResolveGrammar(topRulesTable, actualOptions.errMsgProvider);
}
export function validateGrammar(options: {
rules: Rule[];
tokenTypes: TokenType[];
grammarName: string;
errMsgProvider: IGrammarValidatorErrorMessageProvider;
}): IParserDefinitionError[] {
options = defaults(options, {
errMsgProvider: defaultGrammarValidatorErrorProvider,
});
return orgValidateGrammar(
options.rules,
options.tokenTypes,
options.errMsgProvider,
options.grammarName,
);
}

View File

@@ -0,0 +1,623 @@
import {
clone,
drop,
dropRight,
first as _first,
forEach,
isEmpty,
last,
} from "lodash-es";
import { first } from "./first.js";
import { RestWalker } from "./rest.js";
import { TokenMatcher } from "../parser/parser.js";
import {
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal,
} from "@chevrotain/gast";
import {
IGrammarPath,
IProduction,
ISyntacticContentAssistPath,
IToken,
ITokenGrammarPath,
TokenType,
} from "@chevrotain/types";
export abstract class AbstractNextPossibleTokensWalker extends RestWalker {
protected possibleTokTypes: TokenType[] = [];
protected ruleStack: string[];
protected occurrenceStack: number[];
protected nextProductionName = "";
protected nextProductionOccurrence = 0;
protected found = false;
protected isAtEndOfPath = false;
constructor(
protected topProd: Rule,
protected path: IGrammarPath,
) {
super();
}
startWalking(): TokenType[] {
this.found = false;
if (this.path.ruleStack[0] !== this.topProd.name) {
throw Error("The path does not start with the walker's top Rule!");
}
// immutable for the win
this.ruleStack = clone(this.path.ruleStack).reverse(); // intelij bug requires assertion
this.occurrenceStack = clone(this.path.occurrenceStack).reverse(); // intelij bug requires assertion
// already verified that the first production is valid, we now seek the 2nd production
this.ruleStack.pop();
this.occurrenceStack.pop();
this.updateExpectedNext();
this.walk(this.topProd);
return this.possibleTokTypes;
}
walk(
prod: { definition: IProduction[] },
prevRest: IProduction[] = [],
): void {
// stop scanning once we found the path
if (!this.found) {
super.walk(prod, prevRest);
}
}
walkProdRef(
refProd: NonTerminal,
currRest: IProduction[],
prevRest: IProduction[],
): void {
// found the next production, need to keep walking in it
if (
refProd.referencedRule.name === this.nextProductionName &&
refProd.idx === this.nextProductionOccurrence
) {
const fullRest = currRest.concat(prevRest);
this.updateExpectedNext();
this.walk(refProd.referencedRule, <any>fullRest);
}
}
updateExpectedNext(): void {
// need to consume the Terminal
if (isEmpty(this.ruleStack)) {
// must reset nextProductionXXX to avoid walking down another Top Level production while what we are
// really seeking is the last Terminal...
this.nextProductionName = "";
this.nextProductionOccurrence = 0;
this.isAtEndOfPath = true;
} else {
this.nextProductionName = this.ruleStack.pop()!;
this.nextProductionOccurrence = this.occurrenceStack.pop()!;
}
}
}
export class NextAfterTokenWalker extends AbstractNextPossibleTokensWalker {
private nextTerminalName = "";
private nextTerminalOccurrence = 0;
constructor(
topProd: Rule,
protected path: ITokenGrammarPath,
) {
super(topProd, path);
this.nextTerminalName = this.path.lastTok.name;
this.nextTerminalOccurrence = this.path.lastTokOccurrence;
}
walkTerminal(
terminal: Terminal,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (
this.isAtEndOfPath &&
terminal.terminalType.name === this.nextTerminalName &&
terminal.idx === this.nextTerminalOccurrence &&
!this.found
) {
const fullRest = currRest.concat(prevRest);
const restProd = new Alternative({ definition: fullRest });
this.possibleTokTypes = first(restProd);
this.found = true;
}
}
}
export type AlternativesFirstTokens = TokenType[][];
export interface IFirstAfterRepetition {
token: TokenType | undefined;
occurrence: number | undefined;
isEndOfRule: boolean | undefined;
}
/**
* This walker only "walks" a single "TOP" level in the Grammar Ast, this means
* it never "follows" production refs
*/
export class AbstractNextTerminalAfterProductionWalker extends RestWalker {
protected result: IFirstAfterRepetition = {
token: undefined,
occurrence: undefined,
isEndOfRule: undefined,
};
constructor(
protected topRule: Rule,
protected occurrence: number,
) {
super();
}
startWalking(): IFirstAfterRepetition {
this.walk(this.topRule);
return this.result;
}
}
export class NextTerminalAfterManyWalker extends AbstractNextTerminalAfterProductionWalker {
walkMany(
manyProd: Repetition,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (manyProd.idx === this.occurrence) {
const firstAfterMany = _first(currRest.concat(prevRest));
this.result.isEndOfRule = firstAfterMany === undefined;
if (firstAfterMany instanceof Terminal) {
this.result.token = firstAfterMany.terminalType;
this.result.occurrence = firstAfterMany.idx;
}
} else {
super.walkMany(manyProd, currRest, prevRest);
}
}
}
export class NextTerminalAfterManySepWalker extends AbstractNextTerminalAfterProductionWalker {
walkManySep(
manySepProd: RepetitionWithSeparator,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (manySepProd.idx === this.occurrence) {
const firstAfterManySep = _first(currRest.concat(prevRest));
this.result.isEndOfRule = firstAfterManySep === undefined;
if (firstAfterManySep instanceof Terminal) {
this.result.token = firstAfterManySep.terminalType;
this.result.occurrence = firstAfterManySep.idx;
}
} else {
super.walkManySep(manySepProd, currRest, prevRest);
}
}
}
export class NextTerminalAfterAtLeastOneWalker extends AbstractNextTerminalAfterProductionWalker {
walkAtLeastOne(
atLeastOneProd: RepetitionMandatory,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (atLeastOneProd.idx === this.occurrence) {
const firstAfterAtLeastOne = _first(currRest.concat(prevRest));
this.result.isEndOfRule = firstAfterAtLeastOne === undefined;
if (firstAfterAtLeastOne instanceof Terminal) {
this.result.token = firstAfterAtLeastOne.terminalType;
this.result.occurrence = firstAfterAtLeastOne.idx;
}
} else {
super.walkAtLeastOne(atLeastOneProd, currRest, prevRest);
}
}
}
// TODO: reduce code duplication in the AfterWalkers
export class NextTerminalAfterAtLeastOneSepWalker extends AbstractNextTerminalAfterProductionWalker {
walkAtLeastOneSep(
atleastOneSepProd: RepetitionMandatoryWithSeparator,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (atleastOneSepProd.idx === this.occurrence) {
const firstAfterfirstAfterAtLeastOneSep = _first(
currRest.concat(prevRest),
);
this.result.isEndOfRule = firstAfterfirstAfterAtLeastOneSep === undefined;
if (firstAfterfirstAfterAtLeastOneSep instanceof Terminal) {
this.result.token = firstAfterfirstAfterAtLeastOneSep.terminalType;
this.result.occurrence = firstAfterfirstAfterAtLeastOneSep.idx;
}
} else {
super.walkAtLeastOneSep(atleastOneSepProd, currRest, prevRest);
}
}
}
export interface PartialPathAndSuffixes {
partialPath: TokenType[];
suffixDef: IProduction[];
}
export function possiblePathsFrom(
targetDef: IProduction[],
maxLength: number,
currPath: TokenType[] = [],
): PartialPathAndSuffixes[] {
// avoid side effects
currPath = clone(currPath);
let result: PartialPathAndSuffixes[] = [];
let i = 0;
// TODO: avoid inner funcs
function remainingPathWith(nextDef: IProduction[]) {
return nextDef.concat(drop(targetDef, i + 1));
}
// TODO: avoid inner funcs
function getAlternativesForProd(definition: IProduction[]) {
const alternatives = possiblePathsFrom(
remainingPathWith(definition),
maxLength,
currPath,
);
return result.concat(alternatives);
}
/**
* Mandatory productions will halt the loop as the paths computed from their recursive calls will already contain the
* following (rest) of the targetDef.
*
* For optional productions (Option/Repetition/...) the loop will continue to represent the paths that do not include the
* the optional production.
*/
while (currPath.length < maxLength && i < targetDef.length) {
const prod = targetDef[i];
/* istanbul ignore else */
if (prod instanceof Alternative) {
return getAlternativesForProd(prod.definition);
} else if (prod instanceof NonTerminal) {
return getAlternativesForProd(prod.definition);
} else if (prod instanceof Option) {
result = getAlternativesForProd(prod.definition);
} else if (prod instanceof RepetitionMandatory) {
const newDef = prod.definition.concat([
new Repetition({
definition: prod.definition,
}),
]);
return getAlternativesForProd(newDef);
} else if (prod instanceof RepetitionMandatoryWithSeparator) {
const newDef = [
new Alternative({ definition: prod.definition }),
new Repetition({
definition: [new Terminal({ terminalType: prod.separator })].concat(
<any>prod.definition,
),
}),
];
return getAlternativesForProd(newDef);
} else if (prod instanceof RepetitionWithSeparator) {
const newDef = prod.definition.concat([
new Repetition({
definition: [new Terminal({ terminalType: prod.separator })].concat(
<any>prod.definition,
),
}),
]);
result = getAlternativesForProd(newDef);
} else if (prod instanceof Repetition) {
const newDef = prod.definition.concat([
new Repetition({
definition: prod.definition,
}),
]);
result = getAlternativesForProd(newDef);
} else if (prod instanceof Alternation) {
forEach(prod.definition, (currAlt) => {
// TODO: this is a limited check for empty alternatives
// It would prevent a common case of infinite loops during parser initialization.
// However **in-directly** empty alternatives may still cause issues.
if (isEmpty(currAlt.definition) === false) {
result = getAlternativesForProd(currAlt.definition);
}
});
return result;
} else if (prod instanceof Terminal) {
currPath.push(prod.terminalType);
} else {
throw Error("non exhaustive match");
}
i++;
}
result.push({
partialPath: currPath,
suffixDef: drop(targetDef, i),
});
return result;
}
interface IPathToExamine {
idx: number;
def: IProduction[];
ruleStack: string[];
occurrenceStack: number[];
}
export function nextPossibleTokensAfter(
initialDef: IProduction[],
tokenVector: IToken[],
tokMatcher: TokenMatcher,
maxLookAhead: number,
): ISyntacticContentAssistPath[] {
const EXIT_NON_TERMINAL: any = "EXIT_NONE_TERMINAL";
// to avoid creating a new Array each time.
const EXIT_NON_TERMINAL_ARR = [EXIT_NON_TERMINAL];
const EXIT_ALTERNATIVE: any = "EXIT_ALTERNATIVE";
let foundCompletePath = false;
const tokenVectorLength = tokenVector.length;
const minimalAlternativesIndex = tokenVectorLength - maxLookAhead - 1;
const result: ISyntacticContentAssistPath[] = [];
const possiblePaths: IPathToExamine[] = [];
possiblePaths.push({
idx: -1,
def: initialDef,
ruleStack: [],
occurrenceStack: [],
});
while (!isEmpty(possiblePaths)) {
const currPath = possiblePaths.pop()!;
// skip alternatives if no more results can be found (assuming deterministic grammar with fixed lookahead)
if (currPath === EXIT_ALTERNATIVE) {
if (
foundCompletePath &&
last(possiblePaths)!.idx <= minimalAlternativesIndex
) {
// remove irrelevant alternative
possiblePaths.pop();
}
continue;
}
const currDef = currPath.def;
const currIdx = currPath.idx;
const currRuleStack = currPath.ruleStack;
const currOccurrenceStack = currPath.occurrenceStack;
// For Example: an empty path could exist in a valid grammar in the case of an EMPTY_ALT
if (isEmpty(currDef)) {
continue;
}
const prod = currDef[0];
/* istanbul ignore else */
if (prod === EXIT_NON_TERMINAL) {
const nextPath = {
idx: currIdx,
def: drop(currDef),
ruleStack: dropRight(currRuleStack),
occurrenceStack: dropRight(currOccurrenceStack),
};
possiblePaths.push(nextPath);
} else if (prod instanceof Terminal) {
/* istanbul ignore else */
if (currIdx < tokenVectorLength - 1) {
const nextIdx = currIdx + 1;
const actualToken = tokenVector[nextIdx];
if (tokMatcher!(actualToken, prod.terminalType)) {
const nextPath = {
idx: nextIdx,
def: drop(currDef),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPath);
}
// end of the line
} else if (currIdx === tokenVectorLength - 1) {
// IGNORE ABOVE ELSE
result.push({
nextTokenType: prod.terminalType,
nextTokenOccurrence: prod.idx,
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
});
foundCompletePath = true;
} else {
throw Error("non exhaustive match");
}
} else if (prod instanceof NonTerminal) {
const newRuleStack = clone(currRuleStack);
newRuleStack.push(prod.nonTerminalName);
const newOccurrenceStack = clone(currOccurrenceStack);
newOccurrenceStack.push(prod.idx);
const nextPath = {
idx: currIdx,
def: prod.definition.concat(EXIT_NON_TERMINAL_ARR, drop(currDef)),
ruleStack: newRuleStack,
occurrenceStack: newOccurrenceStack,
};
possiblePaths.push(nextPath);
} else if (prod instanceof Option) {
// the order of alternatives is meaningful, FILO (Last path will be traversed first).
const nextPathWithout = {
idx: currIdx,
def: drop(currDef),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWithout);
// required marker to avoid backtracking paths whose higher priority alternatives already matched
possiblePaths.push(EXIT_ALTERNATIVE);
const nextPathWith = {
idx: currIdx,
def: prod.definition.concat(drop(currDef)),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWith);
} else if (prod instanceof RepetitionMandatory) {
// TODO:(THE NEW operators here take a while...) (convert once?)
const secondIteration = new Repetition({
definition: prod.definition,
idx: prod.idx,
});
const nextDef = prod.definition.concat([secondIteration], drop(currDef));
const nextPath = {
idx: currIdx,
def: nextDef,
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPath);
} else if (prod instanceof RepetitionMandatoryWithSeparator) {
// TODO:(THE NEW operators here take a while...) (convert once?)
const separatorGast = new Terminal({
terminalType: prod.separator,
});
const secondIteration = new Repetition({
definition: [<any>separatorGast].concat(prod.definition),
idx: prod.idx,
});
const nextDef = prod.definition.concat([secondIteration], drop(currDef));
const nextPath = {
idx: currIdx,
def: nextDef,
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPath);
} else if (prod instanceof RepetitionWithSeparator) {
// the order of alternatives is meaningful, FILO (Last path will be traversed first).
const nextPathWithout = {
idx: currIdx,
def: drop(currDef),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWithout);
// required marker to avoid backtracking paths whose higher priority alternatives already matched
possiblePaths.push(EXIT_ALTERNATIVE);
const separatorGast = new Terminal({
terminalType: prod.separator,
});
const nthRepetition = new Repetition({
definition: [<any>separatorGast].concat(prod.definition),
idx: prod.idx,
});
const nextDef = prod.definition.concat([nthRepetition], drop(currDef));
const nextPathWith = {
idx: currIdx,
def: nextDef,
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWith);
} else if (prod instanceof Repetition) {
// the order of alternatives is meaningful, FILO (Last path will be traversed first).
const nextPathWithout = {
idx: currIdx,
def: drop(currDef),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWithout);
// required marker to avoid backtracking paths whose higher priority alternatives already matched
possiblePaths.push(EXIT_ALTERNATIVE);
// TODO: an empty repetition will cause infinite loops here, will the parser detect this in selfAnalysis?
const nthRepetition = new Repetition({
definition: prod.definition,
idx: prod.idx,
});
const nextDef = prod.definition.concat([nthRepetition], drop(currDef));
const nextPathWith = {
idx: currIdx,
def: nextDef,
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(nextPathWith);
} else if (prod instanceof Alternation) {
// the order of alternatives is meaningful, FILO (Last path will be traversed first).
for (let i = prod.definition.length - 1; i >= 0; i--) {
const currAlt: any = prod.definition[i];
const currAltPath = {
idx: currIdx,
def: currAlt.definition.concat(drop(currDef)),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
};
possiblePaths.push(currAltPath);
possiblePaths.push(EXIT_ALTERNATIVE);
}
} else if (prod instanceof Alternative) {
possiblePaths.push({
idx: currIdx,
def: prod.definition.concat(drop(currDef)),
ruleStack: currRuleStack,
occurrenceStack: currOccurrenceStack,
});
} else if (prod instanceof Rule) {
// last because we should only encounter at most a single one of these per invocation.
possiblePaths.push(
expandTopLevelRule(prod, currIdx, currRuleStack, currOccurrenceStack),
);
} else {
throw Error("non exhaustive match");
}
}
return result;
}
function expandTopLevelRule(
topRule: Rule,
currIdx: number,
currRuleStack: string[],
currOccurrenceStack: number[],
): IPathToExamine {
const newRuleStack = clone(currRuleStack);
newRuleStack.push(topRule.name);
const newCurrOccurrenceStack = clone(currOccurrenceStack);
// top rule is always assumed to have been called with occurrence index 1
newCurrOccurrenceStack.push(1);
return {
idx: currIdx,
def: topRule.definition,
ruleStack: newRuleStack,
occurrenceStack: newCurrOccurrenceStack,
};
}

View File

@@ -0,0 +1,33 @@
// Lookahead keys are 32Bit integers in the form
// TTTTTTTT-ZZZZZZZZZZZZ-YYYY-XXXXXXXX
// XXXX -> Occurrence Index bitmap.
// YYYY -> DSL Method Type bitmap.
// ZZZZZZZZZZZZZZZ -> Rule short Index bitmap.
// TTTTTTTTT -> alternation alternative index bitmap
export const BITS_FOR_METHOD_TYPE = 4;
export const BITS_FOR_OCCURRENCE_IDX = 8;
export const BITS_FOR_RULE_IDX = 12;
// TODO: validation, this means that there may at most 2^8 --> 256 alternatives for an alternation.
export const BITS_FOR_ALT_IDX = 8;
// short string used as part of mapping keys.
// being short improves the performance when composing KEYS for maps out of these
// The 5 - 8 bits (16 possible values, are reserved for the DSL method indices)
export const OR_IDX = 1 << BITS_FOR_OCCURRENCE_IDX;
export const OPTION_IDX = 2 << BITS_FOR_OCCURRENCE_IDX;
export const MANY_IDX = 3 << BITS_FOR_OCCURRENCE_IDX;
export const AT_LEAST_ONE_IDX = 4 << BITS_FOR_OCCURRENCE_IDX;
export const MANY_SEP_IDX = 5 << BITS_FOR_OCCURRENCE_IDX;
export const AT_LEAST_ONE_SEP_IDX = 6 << BITS_FOR_OCCURRENCE_IDX;
// this actually returns a number, but it is always used as a string (object prop key)
export function getKeyForAutomaticLookahead(
ruleIdx: number,
dslMethodIdx: number,
occurrence: number,
): number {
return occurrence | dslMethodIdx | ruleIdx;
}
const BITS_START_FOR_ALT_IDX = 32 - BITS_FOR_ALT_IDX;

View File

@@ -0,0 +1,739 @@
import { every, flatten, forEach, has, isEmpty, map, reduce } from "lodash-es";
import { possiblePathsFrom } from "./interpreter.js";
import { RestWalker } from "./rest.js";
import { Predicate, TokenMatcher } from "../parser/parser.js";
import {
tokenStructuredMatcher,
tokenStructuredMatcherNoCategories,
} from "../../scan/tokens.js";
import {
Alternation,
Alternative as AlternativeGAST,
GAstVisitor,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
} from "@chevrotain/gast";
import {
BaseParser,
IOrAlt,
IProduction,
IProductionWithOccurrence,
LookaheadProductionType,
LookaheadSequence,
Rule,
TokenType,
} from "@chevrotain/types";
export enum PROD_TYPE {
OPTION,
REPETITION,
REPETITION_MANDATORY,
REPETITION_MANDATORY_WITH_SEPARATOR,
REPETITION_WITH_SEPARATOR,
ALTERNATION,
}
export function getProdType(
prod: IProduction | LookaheadProductionType,
): PROD_TYPE {
/* istanbul ignore else */
if (prod instanceof Option || prod === "Option") {
return PROD_TYPE.OPTION;
} else if (prod instanceof Repetition || prod === "Repetition") {
return PROD_TYPE.REPETITION;
} else if (
prod instanceof RepetitionMandatory ||
prod === "RepetitionMandatory"
) {
return PROD_TYPE.REPETITION_MANDATORY;
} else if (
prod instanceof RepetitionMandatoryWithSeparator ||
prod === "RepetitionMandatoryWithSeparator"
) {
return PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR;
} else if (
prod instanceof RepetitionWithSeparator ||
prod === "RepetitionWithSeparator"
) {
return PROD_TYPE.REPETITION_WITH_SEPARATOR;
} else if (prod instanceof Alternation || prod === "Alternation") {
return PROD_TYPE.ALTERNATION;
} else {
throw Error("non exhaustive match");
}
}
export function getLookaheadPaths(options: {
occurrence: number;
rule: Rule;
prodType: LookaheadProductionType;
maxLookahead: number;
}): LookaheadSequence[] {
const { occurrence, rule, prodType, maxLookahead } = options;
const type = getProdType(prodType);
if (type === PROD_TYPE.ALTERNATION) {
return getLookaheadPathsForOr(occurrence, rule, maxLookahead);
} else {
return getLookaheadPathsForOptionalProd(
occurrence,
rule,
type,
maxLookahead,
);
}
}
export function buildLookaheadFuncForOr(
occurrence: number,
ruleGrammar: Rule,
maxLookahead: number,
hasPredicates: boolean,
dynamicTokensEnabled: boolean,
laFuncBuilder: Function,
): (orAlts?: IOrAlt<any>[]) => number | undefined {
const lookAheadPaths = getLookaheadPathsForOr(
occurrence,
ruleGrammar,
maxLookahead,
);
const tokenMatcher = areTokenCategoriesNotUsed(lookAheadPaths)
? tokenStructuredMatcherNoCategories
: tokenStructuredMatcher;
return laFuncBuilder(
lookAheadPaths,
hasPredicates,
tokenMatcher,
dynamicTokensEnabled,
);
}
/**
* When dealing with an Optional production (OPTION/MANY/2nd iteration of AT_LEAST_ONE/...) we need to compare
* the lookahead "inside" the production and the lookahead immediately "after" it in the same top level rule (context free).
*
* Example: given a production:
* ABC(DE)?DF
*
* The optional '(DE)?' should only be entered if we see 'DE'. a single Token 'D' is not sufficient to distinguish between the two
* alternatives.
*
* @returns A Lookahead function which will return true IFF the parser should parse the Optional production.
*/
export function buildLookaheadFuncForOptionalProd(
occurrence: number,
ruleGrammar: Rule,
k: number,
dynamicTokensEnabled: boolean,
prodType: PROD_TYPE,
lookaheadBuilder: (
lookAheadSequence: LookaheadSequence,
tokenMatcher: TokenMatcher,
dynamicTokensEnabled: boolean,
) => () => boolean,
): () => boolean {
const lookAheadPaths = getLookaheadPathsForOptionalProd(
occurrence,
ruleGrammar,
prodType,
k,
);
const tokenMatcher = areTokenCategoriesNotUsed(lookAheadPaths)
? tokenStructuredMatcherNoCategories
: tokenStructuredMatcher;
return lookaheadBuilder(
lookAheadPaths[0],
tokenMatcher,
dynamicTokensEnabled,
);
}
export type Alternative = TokenType[][];
export function buildAlternativesLookAheadFunc(
alts: LookaheadSequence[],
hasPredicates: boolean,
tokenMatcher: TokenMatcher,
dynamicTokensEnabled: boolean,
): (orAlts: IOrAlt<any>[]) => number | undefined {
const numOfAlts = alts.length;
const areAllOneTokenLookahead = every(alts, (currAlt) => {
return every(currAlt, (currPath) => {
return currPath.length === 1;
});
});
// This version takes into account the predicates as well.
if (hasPredicates) {
/**
* @returns {number} - The chosen alternative index
*/
return function (
this: BaseParser,
orAlts: IOrAlt<any>[],
): number | undefined {
// unfortunately the predicates must be extracted every single time
// as they cannot be cached due to references to parameters(vars) which are no longer valid.
// note that in the common case of no predicates, no cpu time will be wasted on this (see else block)
const predicates: (Predicate | undefined)[] = map(
orAlts,
(currAlt) => currAlt.GATE,
);
for (let t = 0; t < numOfAlts; t++) {
const currAlt = alts[t];
const currNumOfPaths = currAlt.length;
const currPredicate = predicates[t];
if (currPredicate !== undefined && currPredicate.call(this) === false) {
// if the predicate does not match there is no point in checking the paths
continue;
}
nextPath: for (let j = 0; j < currNumOfPaths; j++) {
const currPath = currAlt[j];
const currPathLength = currPath.length;
for (let i = 0; i < currPathLength; i++) {
const nextToken = this.LA(i + 1);
if (tokenMatcher(nextToken, currPath[i]) === false) {
// mismatch in current path
// try the next pth
continue nextPath;
}
}
// found a full path that matches.
// this will also work for an empty ALT as the loop will be skipped
return t;
}
// none of the paths for the current alternative matched
// try the next alternative
}
// none of the alternatives could be matched
return undefined;
};
} else if (areAllOneTokenLookahead && !dynamicTokensEnabled) {
// optimized (common) case of all the lookaheads paths requiring only
// a single token lookahead. These Optimizations cannot work if dynamically defined Tokens are used.
const singleTokenAlts = map(alts, (currAlt) => {
return flatten(currAlt);
});
const choiceToAlt = reduce(
singleTokenAlts,
(result, currAlt, idx) => {
forEach(currAlt, (currTokType) => {
if (!has(result, currTokType.tokenTypeIdx!)) {
result[currTokType.tokenTypeIdx!] = idx;
}
forEach(currTokType.categoryMatches!, (currExtendingType) => {
if (!has(result, currExtendingType)) {
result[currExtendingType] = idx;
}
});
});
return result;
},
{} as Record<number, number>,
);
/**
* @returns {number} - The chosen alternative index
*/
return function (this: BaseParser): number {
const nextToken = this.LA(1);
return choiceToAlt[nextToken.tokenTypeIdx];
};
} else {
// optimized lookahead without needing to check the predicates at all.
// this causes code duplication which is intentional to improve performance.
/**
* @returns {number} - The chosen alternative index
*/
return function (this: BaseParser): number | undefined {
for (let t = 0; t < numOfAlts; t++) {
const currAlt = alts[t];
const currNumOfPaths = currAlt.length;
nextPath: for (let j = 0; j < currNumOfPaths; j++) {
const currPath = currAlt[j];
const currPathLength = currPath.length;
for (let i = 0; i < currPathLength; i++) {
const nextToken = this.LA(i + 1);
if (tokenMatcher(nextToken, currPath[i]) === false) {
// mismatch in current path
// try the next pth
continue nextPath;
}
}
// found a full path that matches.
// this will also work for an empty ALT as the loop will be skipped
return t;
}
// none of the paths for the current alternative matched
// try the next alternative
}
// none of the alternatives could be matched
return undefined;
};
}
}
export function buildSingleAlternativeLookaheadFunction(
alt: LookaheadSequence,
tokenMatcher: TokenMatcher,
dynamicTokensEnabled: boolean,
): () => boolean {
const areAllOneTokenLookahead = every(alt, (currPath) => {
return currPath.length === 1;
});
const numOfPaths = alt.length;
// optimized (common) case of all the lookaheads paths requiring only
// a single token lookahead.
if (areAllOneTokenLookahead && !dynamicTokensEnabled) {
const singleTokensTypes = flatten(alt);
if (
singleTokensTypes.length === 1 &&
isEmpty((<any>singleTokensTypes[0]).categoryMatches)
) {
const expectedTokenType = singleTokensTypes[0];
const expectedTokenUniqueKey = (<any>expectedTokenType).tokenTypeIdx;
return function (this: BaseParser): boolean {
return this.LA(1).tokenTypeIdx === expectedTokenUniqueKey;
};
} else {
const choiceToAlt = reduce(
singleTokensTypes,
(result, currTokType, idx) => {
result[currTokType.tokenTypeIdx!] = true;
forEach(currTokType.categoryMatches!, (currExtendingType) => {
result[currExtendingType] = true;
});
return result;
},
[] as boolean[],
);
return function (this: BaseParser): boolean {
const nextToken = this.LA(1);
return choiceToAlt[nextToken.tokenTypeIdx] === true;
};
}
} else {
return function (this: BaseParser): boolean {
nextPath: for (let j = 0; j < numOfPaths; j++) {
const currPath = alt[j];
const currPathLength = currPath.length;
for (let i = 0; i < currPathLength; i++) {
const nextToken = this.LA(i + 1);
if (tokenMatcher(nextToken, currPath[i]) === false) {
// mismatch in current path
// try the next pth
continue nextPath;
}
}
// found a full path that matches.
return true;
}
// none of the paths matched
return false;
};
}
}
class RestDefinitionFinderWalker extends RestWalker {
private restDef: IProduction[];
constructor(
private topProd: Rule,
private targetOccurrence: number,
private targetProdType: PROD_TYPE,
) {
super();
}
startWalking(): IProduction[] {
this.walk(this.topProd);
return this.restDef;
}
private checkIsTarget(
node: IProductionWithOccurrence,
expectedProdType: PROD_TYPE,
currRest: IProduction[],
prevRest: IProduction[],
): boolean {
if (
node.idx === this.targetOccurrence &&
this.targetProdType === expectedProdType
) {
this.restDef = currRest.concat(prevRest);
return true;
}
// performance optimization, do not iterate over the entire Grammar ast after we have found the target
return false;
}
walkOption(
optionProd: Option,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (!this.checkIsTarget(optionProd, PROD_TYPE.OPTION, currRest, prevRest)) {
super.walkOption(optionProd, currRest, prevRest);
}
}
walkAtLeastOne(
atLeastOneProd: RepetitionMandatory,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (
!this.checkIsTarget(
atLeastOneProd,
PROD_TYPE.REPETITION_MANDATORY,
currRest,
prevRest,
)
) {
super.walkOption(atLeastOneProd, currRest, prevRest);
}
}
walkAtLeastOneSep(
atLeastOneSepProd: RepetitionMandatoryWithSeparator,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (
!this.checkIsTarget(
atLeastOneSepProd,
PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,
currRest,
prevRest,
)
) {
super.walkOption(atLeastOneSepProd, currRest, prevRest);
}
}
walkMany(
manyProd: Repetition,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (
!this.checkIsTarget(manyProd, PROD_TYPE.REPETITION, currRest, prevRest)
) {
super.walkOption(manyProd, currRest, prevRest);
}
}
walkManySep(
manySepProd: RepetitionWithSeparator,
currRest: IProduction[],
prevRest: IProduction[],
): void {
if (
!this.checkIsTarget(
manySepProd,
PROD_TYPE.REPETITION_WITH_SEPARATOR,
currRest,
prevRest,
)
) {
super.walkOption(manySepProd, currRest, prevRest);
}
}
}
/**
* Returns the definition of a target production in a top level level rule.
*/
class InsideDefinitionFinderVisitor extends GAstVisitor {
public result: IProduction[] = [];
constructor(
private targetOccurrence: number,
private targetProdType: PROD_TYPE,
private targetRef?: any,
) {
super();
}
private checkIsTarget(
node: { definition: IProduction[] } & IProductionWithOccurrence,
expectedProdName: PROD_TYPE,
): void {
if (
node.idx === this.targetOccurrence &&
this.targetProdType === expectedProdName &&
(this.targetRef === undefined || node === this.targetRef)
) {
this.result = node.definition;
}
}
public visitOption(node: Option): void {
this.checkIsTarget(node, PROD_TYPE.OPTION);
}
public visitRepetition(node: Repetition): void {
this.checkIsTarget(node, PROD_TYPE.REPETITION);
}
public visitRepetitionMandatory(node: RepetitionMandatory): void {
this.checkIsTarget(node, PROD_TYPE.REPETITION_MANDATORY);
}
public visitRepetitionMandatoryWithSeparator(
node: RepetitionMandatoryWithSeparator,
): void {
this.checkIsTarget(node, PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR);
}
public visitRepetitionWithSeparator(node: RepetitionWithSeparator): void {
this.checkIsTarget(node, PROD_TYPE.REPETITION_WITH_SEPARATOR);
}
public visitAlternation(node: Alternation): void {
this.checkIsTarget(node, PROD_TYPE.ALTERNATION);
}
}
function initializeArrayOfArrays(size: number): any[][] {
const result = new Array(size);
for (let i = 0; i < size; i++) {
result[i] = [];
}
return result;
}
/**
* A sort of hash function between a Path in the grammar and a string.
* Note that this returns multiple "hashes" to support the scenario of token categories.
* - A single path with categories may match multiple **actual** paths.
*/
function pathToHashKeys(path: TokenType[]): string[] {
let keys = [""];
for (let i = 0; i < path.length; i++) {
const tokType = path[i];
const longerKeys = [];
for (let j = 0; j < keys.length; j++) {
const currShorterKey = keys[j];
longerKeys.push(currShorterKey + "_" + tokType.tokenTypeIdx);
for (let t = 0; t < tokType.categoryMatches!.length; t++) {
const categoriesKeySuffix = "_" + tokType.categoryMatches![t];
longerKeys.push(currShorterKey + categoriesKeySuffix);
}
}
keys = longerKeys;
}
return keys;
}
/**
* Imperative style due to being called from a hot spot
*/
function isUniquePrefixHash(
altKnownPathsKeys: Record<string, boolean>[],
searchPathKeys: string[],
idx: number,
): boolean {
for (
let currAltIdx = 0;
currAltIdx < altKnownPathsKeys.length;
currAltIdx++
) {
// We only want to test vs the other alternatives
if (currAltIdx === idx) {
continue;
}
const otherAltKnownPathsKeys = altKnownPathsKeys[currAltIdx];
for (let searchIdx = 0; searchIdx < searchPathKeys.length; searchIdx++) {
const searchKey = searchPathKeys[searchIdx];
if (otherAltKnownPathsKeys[searchKey] === true) {
return false;
}
}
}
// None of the SearchPathKeys were found in any of the other alternatives
return true;
}
export function lookAheadSequenceFromAlternatives(
altsDefs: IProduction[],
k: number,
): LookaheadSequence[] {
const partialAlts = map(altsDefs, (currAlt) =>
possiblePathsFrom([currAlt], 1),
);
const finalResult = initializeArrayOfArrays(partialAlts.length);
const altsHashes = map(partialAlts, (currAltPaths) => {
const dict: { [key: string]: boolean } = {};
forEach(currAltPaths, (item) => {
const keys = pathToHashKeys(item.partialPath);
forEach(keys, (currKey) => {
dict[currKey] = true;
});
});
return dict;
});
let newData = partialAlts;
// maxLookahead loop
for (let pathLength = 1; pathLength <= k; pathLength++) {
const currDataset = newData;
newData = initializeArrayOfArrays(currDataset.length);
// alternatives loop
for (let altIdx = 0; altIdx < currDataset.length; altIdx++) {
const currAltPathsAndSuffixes = currDataset[altIdx];
// paths in current alternative loop
for (
let currPathIdx = 0;
currPathIdx < currAltPathsAndSuffixes.length;
currPathIdx++
) {
const currPathPrefix = currAltPathsAndSuffixes[currPathIdx].partialPath;
const suffixDef = currAltPathsAndSuffixes[currPathIdx].suffixDef;
const prefixKeys = pathToHashKeys(currPathPrefix);
const isUnique = isUniquePrefixHash(altsHashes, prefixKeys, altIdx);
// End of the line for this path.
if (isUnique || isEmpty(suffixDef) || currPathPrefix.length === k) {
const currAltResult = finalResult[altIdx];
// TODO: Can we implement a containsPath using Maps/Dictionaries?
if (containsPath(currAltResult, currPathPrefix) === false) {
currAltResult.push(currPathPrefix);
// Update all new keys for the current path.
for (let j = 0; j < prefixKeys.length; j++) {
const currKey = prefixKeys[j];
altsHashes[altIdx][currKey] = true;
}
}
}
// Expand longer paths
else {
const newPartialPathsAndSuffixes = possiblePathsFrom(
suffixDef,
pathLength + 1,
currPathPrefix,
);
newData[altIdx] = newData[altIdx].concat(newPartialPathsAndSuffixes);
// Update keys for new known paths
forEach(newPartialPathsAndSuffixes, (item) => {
const prefixKeys = pathToHashKeys(item.partialPath);
forEach(prefixKeys, (key) => {
altsHashes[altIdx][key] = true;
});
});
}
}
}
}
return finalResult;
}
export function getLookaheadPathsForOr(
occurrence: number,
ruleGrammar: Rule,
k: number,
orProd?: Alternation,
): LookaheadSequence[] {
const visitor = new InsideDefinitionFinderVisitor(
occurrence,
PROD_TYPE.ALTERNATION,
orProd,
);
ruleGrammar.accept(visitor);
return lookAheadSequenceFromAlternatives(visitor.result, k);
}
export function getLookaheadPathsForOptionalProd(
occurrence: number,
ruleGrammar: Rule,
prodType: PROD_TYPE,
k: number,
): LookaheadSequence[] {
const insideDefVisitor = new InsideDefinitionFinderVisitor(
occurrence,
prodType,
);
ruleGrammar.accept(insideDefVisitor);
const insideDef = insideDefVisitor.result;
const afterDefWalker = new RestDefinitionFinderWalker(
ruleGrammar,
occurrence,
prodType,
);
const afterDef = afterDefWalker.startWalking();
const insideFlat = new AlternativeGAST({ definition: insideDef });
const afterFlat = new AlternativeGAST({ definition: afterDef });
return lookAheadSequenceFromAlternatives([insideFlat, afterFlat], k);
}
export function containsPath(
alternative: Alternative,
searchPath: TokenType[],
): boolean {
compareOtherPath: for (let i = 0; i < alternative.length; i++) {
const otherPath = alternative[i];
if (otherPath.length !== searchPath.length) {
continue;
}
for (let j = 0; j < otherPath.length; j++) {
const searchTok = searchPath[j];
const otherTok = otherPath[j];
const matchingTokens =
searchTok === otherTok ||
otherTok.categoryMatchesMap![searchTok.tokenTypeIdx!] !== undefined;
if (matchingTokens === false) {
continue compareOtherPath;
}
}
return true;
}
return false;
}
export function isStrictPrefixOfPath(
prefix: TokenType[],
other: TokenType[],
): boolean {
return (
prefix.length < other.length &&
every(prefix, (tokType, idx) => {
const otherTokType = other[idx];
return (
tokType === otherTokType ||
otherTokType.categoryMatchesMap![tokType.tokenTypeIdx!]
);
})
);
}
export function areTokenCategoriesNotUsed(
lookAheadPaths: LookaheadSequence[],
): boolean {
return every(lookAheadPaths, (singleAltPaths) =>
every(singleAltPaths, (singlePath) =>
every(singlePath, (token) => isEmpty(token.categoryMatches!)),
),
);
}

View File

@@ -0,0 +1,57 @@
import {
IParserUnresolvedRefDefinitionError,
ParserDefinitionErrorType,
} from "../parser/parser.js";
import { forEach, values } from "lodash-es";
import { GAstVisitor, NonTerminal, Rule } from "@chevrotain/gast";
import {
IGrammarResolverErrorMessageProvider,
IParserDefinitionError,
} from "./types.js";
export function resolveGrammar(
topLevels: Record<string, Rule>,
errMsgProvider: IGrammarResolverErrorMessageProvider,
): IParserDefinitionError[] {
const refResolver = new GastRefResolverVisitor(topLevels, errMsgProvider);
refResolver.resolveRefs();
return refResolver.errors;
}
export class GastRefResolverVisitor extends GAstVisitor {
public errors: IParserUnresolvedRefDefinitionError[] = [];
private currTopLevel: Rule;
constructor(
private nameToTopRule: Record<string, Rule>,
private errMsgProvider: IGrammarResolverErrorMessageProvider,
) {
super();
}
public resolveRefs(): void {
forEach(values(this.nameToTopRule), (prod) => {
this.currTopLevel = prod;
prod.accept(this);
});
}
public visitNonTerminal(node: NonTerminal): void {
const ref = this.nameToTopRule[node.nonTerminalName];
if (!ref) {
const msg = this.errMsgProvider.buildRuleNotFoundError(
this.currTopLevel,
node,
);
this.errors.push({
message: msg,
type: ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,
ruleName: this.currTopLevel.name,
unresolvedRefName: node.nonTerminalName,
});
} else {
node.referencedRule = ref;
}
}
}

View File

@@ -0,0 +1,314 @@
import { clone, forEach, has, isEmpty, map, values } from "lodash-es";
import { toFastProperties } from "@chevrotain/utils";
import { computeAllProdsFollows } from "../grammar/follow.js";
import { createTokenInstance, EOF } from "../../scan/tokens_public.js";
import {
defaultGrammarValidatorErrorProvider,
defaultParserErrorProvider,
} from "../errors_public.js";
import {
resolveGrammar,
validateGrammar,
} from "../grammar/gast/gast_resolver_public.js";
import {
CstNode,
IParserConfig,
IRecognitionException,
IRuleConfig,
IToken,
TokenType,
TokenVocabulary,
} from "@chevrotain/types";
import { Recoverable } from "./traits/recoverable.js";
import { LooksAhead } from "./traits/looksahead.js";
import { TreeBuilder } from "./traits/tree_builder.js";
import { LexerAdapter } from "./traits/lexer_adapter.js";
import { RecognizerApi } from "./traits/recognizer_api.js";
import { RecognizerEngine } from "./traits/recognizer_engine.js";
import { ErrorHandler } from "./traits/error_handler.js";
import { MixedInParser } from "./traits/parser_traits.js";
import { ContentAssist } from "./traits/context_assist.js";
import { GastRecorder } from "./traits/gast_recorder.js";
import { PerformanceTracer } from "./traits/perf_tracer.js";
import { applyMixins } from "./utils/apply_mixins.js";
import { IParserDefinitionError } from "../grammar/types.js";
import { Rule } from "@chevrotain/gast";
import { IParserConfigInternal, ParserMethodInternal } from "./types.js";
import { validateLookahead } from "../grammar/checks.js";
export const END_OF_FILE = createTokenInstance(
EOF,
"",
NaN,
NaN,
NaN,
NaN,
NaN,
NaN,
);
Object.freeze(END_OF_FILE);
export type TokenMatcher = (token: IToken, tokType: TokenType) => boolean;
export const DEFAULT_PARSER_CONFIG: Required<
Omit<IParserConfigInternal, "lookaheadStrategy">
> = Object.freeze({
recoveryEnabled: false,
maxLookahead: 3,
dynamicTokensEnabled: false,
outputCst: true,
errorMessageProvider: defaultParserErrorProvider,
nodeLocationTracking: "none",
traceInitPerf: false,
skipValidations: false,
});
export const DEFAULT_RULE_CONFIG: Required<IRuleConfig<any>> = Object.freeze({
recoveryValueFunc: () => undefined,
resyncEnabled: true,
});
export enum ParserDefinitionErrorType {
INVALID_RULE_NAME = 0,
DUPLICATE_RULE_NAME = 1,
INVALID_RULE_OVERRIDE = 2,
DUPLICATE_PRODUCTIONS = 3,
UNRESOLVED_SUBRULE_REF = 4,
LEFT_RECURSION = 5,
NONE_LAST_EMPTY_ALT = 6,
AMBIGUOUS_ALTS = 7,
CONFLICT_TOKENS_RULES_NAMESPACE = 8,
INVALID_TOKEN_NAME = 9,
NO_NON_EMPTY_LOOKAHEAD = 10,
AMBIGUOUS_PREFIX_ALTS = 11,
TOO_MANY_ALTS = 12,
CUSTOM_LOOKAHEAD_VALIDATION = 13,
}
export interface IParserDuplicatesDefinitionError extends IParserDefinitionError {
dslName: string;
occurrence: number;
parameter?: string;
}
export interface IParserEmptyAlternativeDefinitionError extends IParserDefinitionError {
occurrence: number;
alternative: number;
}
export interface IParserAmbiguousAlternativesDefinitionError extends IParserDefinitionError {
occurrence: number | string;
alternatives: number[];
}
export interface IParserUnresolvedRefDefinitionError extends IParserDefinitionError {
unresolvedRefName: string;
}
export interface IParserState {
errors: IRecognitionException[];
lexerState: any;
RULE_STACK: number[];
CST_STACK: CstNode[];
}
export type Predicate = () => boolean;
export function EMPTY_ALT(): () => undefined;
export function EMPTY_ALT<T>(value: T): () => T;
export function EMPTY_ALT(value: any = undefined) {
return function () {
return value;
};
}
export class Parser {
// Set this flag to true if you don't want the Parser to throw error when problems in it's definition are detected.
// (normally during the parser's constructor).
// This is a design time flag, it will not affect the runtime error handling of the parser, just design time errors,
// for example: duplicate rule names, referencing an unresolved subrule, etc...
// This flag should not be enabled during normal usage, it is used in special situations, for example when
// needing to display the parser definition errors in some GUI(online playground).
static DEFER_DEFINITION_ERRORS_HANDLING: boolean = false;
/**
* @deprecated use the **instance** method with the same name instead
*/
static performSelfAnalysis(parserInstance: Parser): void {
throw Error(
"The **static** `performSelfAnalysis` method has been deprecated." +
"\t\nUse the **instance** method with the same name instead.",
);
}
public performSelfAnalysis(this: MixedInParser): void {
this.TRACE_INIT("performSelfAnalysis", () => {
let defErrorsMsgs;
this.selfAnalysisDone = true;
const className = this.className;
this.TRACE_INIT("toFastProps", () => {
// Without this voodoo magic the parser would be x3-x4 slower
// It seems it is better to invoke `toFastProperties` **before**
// Any manipulations of the `this` object done during the recording phase.
toFastProperties(this);
});
this.TRACE_INIT("Grammar Recording", () => {
try {
this.enableRecording();
// Building the GAST
forEach(this.definedRulesNames, (currRuleName) => {
const wrappedRule = (this as any)[
currRuleName
] as ParserMethodInternal<unknown[], unknown>;
const originalGrammarAction = wrappedRule["originalGrammarAction"];
let recordedRuleGast!: Rule;
this.TRACE_INIT(`${currRuleName} Rule`, () => {
recordedRuleGast = this.topLevelRuleRecord(
currRuleName,
originalGrammarAction,
);
});
this.gastProductionsCache[currRuleName] = recordedRuleGast;
});
} finally {
this.disableRecording();
}
});
let resolverErrors: IParserDefinitionError[] = [];
this.TRACE_INIT("Grammar Resolving", () => {
resolverErrors = resolveGrammar({
rules: values(this.gastProductionsCache),
});
this.definitionErrors = this.definitionErrors.concat(resolverErrors);
});
this.TRACE_INIT("Grammar Validations", () => {
// only perform additional grammar validations IFF no resolving errors have occurred.
// as unresolved grammar may lead to unhandled runtime exceptions in the follow up validations.
if (isEmpty(resolverErrors) && this.skipValidations === false) {
const validationErrors = validateGrammar({
rules: values(this.gastProductionsCache),
tokenTypes: values(this.tokensMap),
errMsgProvider: defaultGrammarValidatorErrorProvider,
grammarName: className,
});
const lookaheadValidationErrors = validateLookahead({
lookaheadStrategy: this.lookaheadStrategy,
rules: values(this.gastProductionsCache),
tokenTypes: values(this.tokensMap),
grammarName: className,
});
this.definitionErrors = this.definitionErrors.concat(
validationErrors,
lookaheadValidationErrors,
);
}
});
// this analysis may fail if the grammar is not perfectly valid
if (isEmpty(this.definitionErrors)) {
// The results of these computations are not needed unless error recovery is enabled.
if (this.recoveryEnabled) {
this.TRACE_INIT("computeAllProdsFollows", () => {
const allFollows = computeAllProdsFollows(
values(this.gastProductionsCache),
);
this.resyncFollows = allFollows;
});
}
this.TRACE_INIT("ComputeLookaheadFunctions", () => {
this.lookaheadStrategy.initialize?.({
rules: values(this.gastProductionsCache),
});
this.preComputeLookaheadFunctions(values(this.gastProductionsCache));
});
}
if (
!Parser.DEFER_DEFINITION_ERRORS_HANDLING &&
!isEmpty(this.definitionErrors)
) {
defErrorsMsgs = map(
this.definitionErrors,
(defError) => defError.message,
);
throw new Error(
`Parser Definition Errors detected:\n ${defErrorsMsgs.join(
"\n-------------------------------\n",
)}`,
);
}
});
}
definitionErrors: IParserDefinitionError[] = [];
selfAnalysisDone = false;
protected skipValidations: boolean;
constructor(tokenVocabulary: TokenVocabulary, config: IParserConfig) {
const that: MixedInParser = this as any;
that.initErrorHandler(config);
that.initLexerAdapter();
that.initLooksAhead(config);
that.initRecognizerEngine(tokenVocabulary, config);
that.initRecoverable(config);
that.initTreeBuilder(config);
that.initContentAssist();
that.initGastRecorder(config);
that.initPerformanceTracer(config);
if (has(config, "ignoredIssues")) {
throw new Error(
"The <ignoredIssues> IParserConfig property has been deprecated.\n\t" +
"Please use the <IGNORE_AMBIGUITIES> flag on the relevant DSL method instead.\n\t" +
"See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES\n\t" +
"For further details.",
);
}
this.skipValidations = has(config, "skipValidations")
? (config.skipValidations as boolean) // casting assumes the end user passing the correct type
: DEFAULT_PARSER_CONFIG.skipValidations;
}
}
applyMixins(Parser, [
Recoverable,
LooksAhead,
TreeBuilder,
LexerAdapter,
RecognizerEngine,
RecognizerApi,
ErrorHandler,
ContentAssist,
GastRecorder,
PerformanceTracer,
]);
export class CstParser extends Parser {
constructor(
tokenVocabulary: TokenVocabulary,
config: IParserConfigInternal = DEFAULT_PARSER_CONFIG,
) {
const configClone = clone(config);
configClone.outputCst = true;
super(tokenVocabulary, configClone);
}
}
export class EmbeddedActionsParser extends Parser {
constructor(
tokenVocabulary: TokenVocabulary,
config: IParserConfigInternal = DEFAULT_PARSER_CONFIG,
) {
const configClone = clone(config);
configClone.outputCst = false;
super(tokenVocabulary, configClone);
}
}

View File

@@ -0,0 +1,269 @@
import { forEach, has } from "lodash-es";
import { DEFAULT_PARSER_CONFIG } from "../parser.js";
import {
ILookaheadStrategy,
IParserConfig,
OptionalProductionType,
} from "@chevrotain/types";
import {
AT_LEAST_ONE_IDX,
AT_LEAST_ONE_SEP_IDX,
getKeyForAutomaticLookahead,
MANY_IDX,
MANY_SEP_IDX,
OPTION_IDX,
OR_IDX,
} from "../../grammar/keys.js";
import { MixedInParser } from "./parser_traits.js";
import {
Alternation,
GAstVisitor,
getProductionDslName,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
} from "@chevrotain/gast";
import { LLkLookaheadStrategy } from "../../grammar/llk_lookahead.js";
/**
* Trait responsible for the lookahead related utilities and optimizations.
*/
export class LooksAhead {
maxLookahead: number;
lookAheadFuncsCache: any;
dynamicTokensEnabled: boolean;
lookaheadStrategy: ILookaheadStrategy;
initLooksAhead(config: IParserConfig) {
this.dynamicTokensEnabled = has(config, "dynamicTokensEnabled")
? (config.dynamicTokensEnabled as boolean) // assumes end user provides the correct config value/type
: DEFAULT_PARSER_CONFIG.dynamicTokensEnabled;
this.maxLookahead = has(config, "maxLookahead")
? (config.maxLookahead as number) // assumes end user provides the correct config value/type
: DEFAULT_PARSER_CONFIG.maxLookahead;
this.lookaheadStrategy = has(config, "lookaheadStrategy")
? (config.lookaheadStrategy as ILookaheadStrategy) // assumes end user provides the correct config value/type
: new LLkLookaheadStrategy({ maxLookahead: this.maxLookahead });
this.lookAheadFuncsCache = new Map();
}
preComputeLookaheadFunctions(this: MixedInParser, rules: Rule[]): void {
forEach(rules, (currRule) => {
this.TRACE_INIT(`${currRule.name} Rule Lookahead`, () => {
const {
alternation,
repetition,
option,
repetitionMandatory,
repetitionMandatoryWithSeparator,
repetitionWithSeparator,
} = collectMethods(currRule);
forEach(alternation, (currProd) => {
const prodIdx = currProd.idx === 0 ? "" : currProd.idx;
this.TRACE_INIT(`${getProductionDslName(currProd)}${prodIdx}`, () => {
const laFunc = this.lookaheadStrategy.buildLookaheadForAlternation({
prodOccurrence: currProd.idx,
rule: currRule,
maxLookahead: currProd.maxLookahead || this.maxLookahead,
hasPredicates: currProd.hasPredicates,
dynamicTokensEnabled: this.dynamicTokensEnabled,
});
const key = getKeyForAutomaticLookahead(
this.fullRuleNameToShort[currRule.name],
OR_IDX,
currProd.idx,
);
this.setLaFuncCache(key, laFunc);
});
});
forEach(repetition, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
MANY_IDX,
"Repetition",
currProd.maxLookahead,
getProductionDslName(currProd),
);
});
forEach(option, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
OPTION_IDX,
"Option",
currProd.maxLookahead,
getProductionDslName(currProd),
);
});
forEach(repetitionMandatory, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
AT_LEAST_ONE_IDX,
"RepetitionMandatory",
currProd.maxLookahead,
getProductionDslName(currProd),
);
});
forEach(repetitionMandatoryWithSeparator, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
AT_LEAST_ONE_SEP_IDX,
"RepetitionMandatoryWithSeparator",
currProd.maxLookahead,
getProductionDslName(currProd),
);
});
forEach(repetitionWithSeparator, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
MANY_SEP_IDX,
"RepetitionWithSeparator",
currProd.maxLookahead,
getProductionDslName(currProd),
);
});
});
});
}
computeLookaheadFunc(
this: MixedInParser,
rule: Rule,
prodOccurrence: number,
prodKey: number,
prodType: OptionalProductionType,
prodMaxLookahead: number | undefined,
dslMethodName: string,
): void {
this.TRACE_INIT(
`${dslMethodName}${prodOccurrence === 0 ? "" : prodOccurrence}`,
() => {
const laFunc = this.lookaheadStrategy.buildLookaheadForOptional({
prodOccurrence,
rule,
maxLookahead: prodMaxLookahead || this.maxLookahead,
dynamicTokensEnabled: this.dynamicTokensEnabled,
prodType,
});
const key = getKeyForAutomaticLookahead(
this.fullRuleNameToShort[rule.name],
prodKey,
prodOccurrence,
);
this.setLaFuncCache(key, laFunc);
},
);
}
// this actually returns a number, but it is always used as a string (object prop key)
getKeyForAutomaticLookahead(
this: MixedInParser,
dslMethodIdx: number,
occurrence: number,
): number {
const currRuleShortName: any = this.getLastExplicitRuleShortName();
return getKeyForAutomaticLookahead(
currRuleShortName,
dslMethodIdx,
occurrence,
);
}
getLaFuncFromCache(this: MixedInParser, key: number): Function {
return this.lookAheadFuncsCache.get(key);
}
/* istanbul ignore next */
setLaFuncCache(this: MixedInParser, key: number, value: Function): void {
this.lookAheadFuncsCache.set(key, value);
}
}
class DslMethodsCollectorVisitor extends GAstVisitor {
public dslMethods: {
option: Option[];
alternation: Alternation[];
repetition: Repetition[];
repetitionWithSeparator: RepetitionWithSeparator[];
repetitionMandatory: RepetitionMandatory[];
repetitionMandatoryWithSeparator: RepetitionMandatoryWithSeparator[];
} = {
option: [],
alternation: [],
repetition: [],
repetitionWithSeparator: [],
repetitionMandatory: [],
repetitionMandatoryWithSeparator: [],
};
reset() {
this.dslMethods = {
option: [],
alternation: [],
repetition: [],
repetitionWithSeparator: [],
repetitionMandatory: [],
repetitionMandatoryWithSeparator: [],
};
}
public visitOption(option: Option): void {
this.dslMethods.option.push(option);
}
public visitRepetitionWithSeparator(manySep: RepetitionWithSeparator): void {
this.dslMethods.repetitionWithSeparator.push(manySep);
}
public visitRepetitionMandatory(atLeastOne: RepetitionMandatory): void {
this.dslMethods.repetitionMandatory.push(atLeastOne);
}
public visitRepetitionMandatoryWithSeparator(
atLeastOneSep: RepetitionMandatoryWithSeparator,
): void {
this.dslMethods.repetitionMandatoryWithSeparator.push(atLeastOneSep);
}
public visitRepetition(many: Repetition): void {
this.dslMethods.repetition.push(many);
}
public visitAlternation(or: Alternation): void {
this.dslMethods.alternation.push(or);
}
}
const collectorVisitor = new DslMethodsCollectorVisitor();
export function collectMethods(rule: Rule): {
option: Option[];
alternation: Alternation[];
repetition: Repetition[];
repetitionWithSeparator: RepetitionWithSeparator[];
repetitionMandatory: RepetitionMandatory[];
repetitionMandatoryWithSeparator: RepetitionMandatoryWithSeparator[];
} {
collectorVisitor.reset();
rule.accept(collectorVisitor);
const dslMethods = collectorVisitor.dslMethods;
// avoid uncleaned references
collectorVisitor.reset();
return <any>dslMethods;
}

View File

@@ -0,0 +1,54 @@
import { IParserConfig } from "@chevrotain/types";
import { has } from "lodash-es";
import { timer } from "@chevrotain/utils";
import { MixedInParser } from "./parser_traits.js";
import { DEFAULT_PARSER_CONFIG } from "../parser.js";
/**
* Trait responsible for runtime parsing errors.
*/
export class PerformanceTracer {
traceInitPerf: boolean | number;
traceInitMaxIdent: number;
traceInitIndent: number;
initPerformanceTracer(config: IParserConfig) {
if (has(config, "traceInitPerf")) {
const userTraceInitPerf = config.traceInitPerf;
const traceIsNumber = typeof userTraceInitPerf === "number";
this.traceInitMaxIdent = traceIsNumber
? <number>userTraceInitPerf
: Infinity;
this.traceInitPerf = traceIsNumber
? userTraceInitPerf > 0
: (userTraceInitPerf as boolean); // assumes end user provides the correct config value/type
} else {
this.traceInitMaxIdent = 0;
this.traceInitPerf = DEFAULT_PARSER_CONFIG.traceInitPerf;
}
this.traceInitIndent = -1;
}
TRACE_INIT<T>(this: MixedInParser, phaseDesc: string, phaseImpl: () => T): T {
// No need to optimize this using NOOP pattern because
// It is not called in a hot spot...
if (this.traceInitPerf === true) {
this.traceInitIndent++;
const indent = new Array(this.traceInitIndent + 1).join("\t");
if (this.traceInitIndent < this.traceInitMaxIdent) {
console.log(`${indent}--> <${phaseDesc}>`);
}
const { time, value } = timer(phaseImpl);
/* istanbul ignore next - Difficult to reproduce specific performance behavior (>10ms) in tests */
const traceMethod = time > 10 ? console.warn : console.log;
if (this.traceInitIndent < this.traceInitMaxIdent) {
traceMethod(`${indent}<-- <${phaseDesc}> time: ${time}ms`);
}
this.traceInitIndent--;
return value;
} else {
return phaseImpl();
}
}
}

View File

@@ -0,0 +1,719 @@
import {
AtLeastOneSepMethodOpts,
ConsumeMethodOpts,
DSLMethodOpts,
DSLMethodOptsWithErr,
GrammarAction,
IOrAlt,
IRuleConfig,
ISerializedGast,
IToken,
ManySepMethodOpts,
OrMethodOpts,
SubruleMethodOpts,
TokenType,
} from "@chevrotain/types";
import { includes, values } from "lodash-es";
import { isRecognitionException } from "../../exceptions_public.js";
import { DEFAULT_RULE_CONFIG, ParserDefinitionErrorType } from "../parser.js";
import { defaultGrammarValidatorErrorProvider } from "../../errors_public.js";
import { validateRuleIsOverridden } from "../../grammar/checks.js";
import { MixedInParser } from "./parser_traits.js";
import { Rule, serializeGrammar } from "@chevrotain/gast";
import { IParserDefinitionError } from "../../grammar/types.js";
import { ParserMethodInternal } from "../types.js";
/**
* This trait is responsible for implementing the public API
* for defining Chevrotain parsers, i.e:
* - CONSUME
* - RULE
* - OPTION
* - ...
*/
export class RecognizerApi {
ACTION<T>(this: MixedInParser, impl: () => T): T {
return impl.call(this);
}
consume(
this: MixedInParser,
idx: number,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, idx, options);
}
subrule<ARGS extends unknown[], R>(
this: MixedInParser,
idx: number,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, idx, options);
}
option<OUT>(
this: MixedInParser,
idx: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, idx);
}
or(
this: MixedInParser,
idx: number,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<any>,
): any {
return this.orInternal(altsOrOpts, idx);
}
many(
this: MixedInParser,
idx: number,
actionORMethodDef: GrammarAction<any> | DSLMethodOpts<any>,
): void {
return this.manyInternal(idx, actionORMethodDef);
}
atLeastOne(
this: MixedInParser,
idx: number,
actionORMethodDef: GrammarAction<any> | DSLMethodOptsWithErr<any>,
): void {
return this.atLeastOneInternal(idx, actionORMethodDef);
}
CONSUME(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 0, options);
}
CONSUME1(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 1, options);
}
CONSUME2(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 2, options);
}
CONSUME3(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 3, options);
}
CONSUME4(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 4, options);
}
CONSUME5(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 5, options);
}
CONSUME6(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 6, options);
}
CONSUME7(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 7, options);
}
CONSUME8(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 8, options);
}
CONSUME9(
this: MixedInParser,
tokType: TokenType,
options?: ConsumeMethodOpts,
): IToken {
return this.consumeInternal(tokType, 9, options);
}
SUBRULE<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 0, options);
}
SUBRULE1<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 1, options);
}
SUBRULE2<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 2, options);
}
SUBRULE3<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 3, options);
}
SUBRULE4<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 4, options);
}
SUBRULE5<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 5, options);
}
SUBRULE6<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 6, options);
}
SUBRULE7<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 7, options);
}
SUBRULE8<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 8, options);
}
SUBRULE9<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
options?: SubruleMethodOpts<ARGS>,
): R {
return this.subruleInternal(ruleToCall, 9, options);
}
OPTION<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 0);
}
OPTION1<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 1);
}
OPTION2<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 2);
}
OPTION3<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 3);
}
OPTION4<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 4);
}
OPTION5<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 5);
}
OPTION6<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 6);
}
OPTION7<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 7);
}
OPTION8<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 8);
}
OPTION9<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): OUT | undefined {
return this.optionInternal(actionORMethodDef, 9);
}
OR<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 0);
}
OR1<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 1);
}
OR2<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 2);
}
OR3<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 3);
}
OR4<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 4);
}
OR5<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 5);
}
OR6<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 6);
}
OR7<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 7);
}
OR8<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 8);
}
OR9<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
): T {
return this.orInternal(altsOrOpts, 9);
}
MANY<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(0, actionORMethodDef);
}
MANY1<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(1, actionORMethodDef);
}
MANY2<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(2, actionORMethodDef);
}
MANY3<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(3, actionORMethodDef);
}
MANY4<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(4, actionORMethodDef);
}
MANY5<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(5, actionORMethodDef);
}
MANY6<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(6, actionORMethodDef);
}
MANY7<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(7, actionORMethodDef);
}
MANY8<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(8, actionORMethodDef);
}
MANY9<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
this.manyInternal(9, actionORMethodDef);
}
MANY_SEP<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(0, options);
}
MANY_SEP1<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(1, options);
}
MANY_SEP2<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(2, options);
}
MANY_SEP3<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(3, options);
}
MANY_SEP4<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(4, options);
}
MANY_SEP5<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(5, options);
}
MANY_SEP6<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(6, options);
}
MANY_SEP7<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(7, options);
}
MANY_SEP8<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(8, options);
}
MANY_SEP9<OUT>(this: MixedInParser, options: ManySepMethodOpts<OUT>): void {
this.manySepFirstInternal(9, options);
}
AT_LEAST_ONE<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(0, actionORMethodDef);
}
AT_LEAST_ONE1<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
return this.atLeastOneInternal(1, actionORMethodDef);
}
AT_LEAST_ONE2<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(2, actionORMethodDef);
}
AT_LEAST_ONE3<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(3, actionORMethodDef);
}
AT_LEAST_ONE4<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(4, actionORMethodDef);
}
AT_LEAST_ONE5<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(5, actionORMethodDef);
}
AT_LEAST_ONE6<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(6, actionORMethodDef);
}
AT_LEAST_ONE7<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(7, actionORMethodDef);
}
AT_LEAST_ONE8<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(8, actionORMethodDef);
}
AT_LEAST_ONE9<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
this.atLeastOneInternal(9, actionORMethodDef);
}
AT_LEAST_ONE_SEP<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(0, options);
}
AT_LEAST_ONE_SEP1<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(1, options);
}
AT_LEAST_ONE_SEP2<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(2, options);
}
AT_LEAST_ONE_SEP3<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(3, options);
}
AT_LEAST_ONE_SEP4<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(4, options);
}
AT_LEAST_ONE_SEP5<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(5, options);
}
AT_LEAST_ONE_SEP6<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(6, options);
}
AT_LEAST_ONE_SEP7<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(7, options);
}
AT_LEAST_ONE_SEP8<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(8, options);
}
AT_LEAST_ONE_SEP9<OUT>(
this: MixedInParser,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
this.atLeastOneSepFirstInternal(9, options);
}
RULE<T>(
this: MixedInParser,
name: string,
implementation: (...implArgs: any[]) => T,
config: IRuleConfig<T> = DEFAULT_RULE_CONFIG,
): (idxInCallingRule?: number, ...args: any[]) => T | any {
if (includes(this.definedRulesNames, name)) {
const errMsg =
defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({
topLevelRule: name,
grammarName: this.className,
});
const error = {
message: errMsg,
type: ParserDefinitionErrorType.DUPLICATE_RULE_NAME,
ruleName: name,
};
this.definitionErrors.push(error);
}
this.definedRulesNames.push(name);
const ruleImplementation = this.defineRule(name, implementation, config);
(this as any)[name] = ruleImplementation;
return ruleImplementation;
}
OVERRIDE_RULE<T>(
this: MixedInParser,
name: string,
impl: (...implArgs: any[]) => T,
config: IRuleConfig<T> = DEFAULT_RULE_CONFIG,
): (idxInCallingRule?: number, ...args: any[]) => T {
const ruleErrors: IParserDefinitionError[] = validateRuleIsOverridden(
name,
this.definedRulesNames,
this.className,
);
this.definitionErrors = this.definitionErrors.concat(ruleErrors);
const ruleImplementation = this.defineRule(name, impl, config);
(this as any)[name] = ruleImplementation;
return ruleImplementation;
}
BACKTRACK<T>(
this: MixedInParser,
grammarRule: (...args: any[]) => T,
args?: any[],
): () => boolean {
return function () {
// save org state
this.isBackTrackingStack.push(1);
const orgState = this.saveRecogState();
try {
grammarRule.apply(this, args);
// if no exception was thrown we have succeed parsing the rule.
return true;
} catch (e) {
if (isRecognitionException(e)) {
return false;
} else {
throw e;
}
} finally {
this.reloadRecogState(orgState);
this.isBackTrackingStack.pop();
}
};
}
// GAST export APIs
public getGAstProductions(this: MixedInParser): Record<string, Rule> {
return this.gastProductionsCache;
}
public getSerializedGastProductions(this: MixedInParser): ISerializedGast[] {
return serializeGrammar(values(this.gastProductionsCache));
}
}

1165
frontend/node_modules/chevrotain/src/scan/lexer.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

323
frontend/node_modules/chevrotain/src/scan/reg_exp.ts generated vendored Normal file
View File

@@ -0,0 +1,323 @@
import {
Alternative,
Atom,
BaseRegExpVisitor,
Character,
Disjunction,
Group,
Set,
} from "@chevrotain/regexp-to-ast";
import { every, find, forEach, includes, isArray, values } from "lodash-es";
import { PRINT_ERROR, PRINT_WARNING } from "@chevrotain/utils";
import { ASTNode, getRegExpAst } from "./reg_exp_parser.js";
import { charCodeToOptimizedIndex, minOptimizationVal } from "./lexer.js";
const complementErrorMessage =
"Complement Sets are not supported for first char optimization";
export const failedOptimizationPrefixMsg =
'Unable to use "first char" lexer optimizations:\n';
export function getOptimizedStartCodesIndices(
regExp: RegExp,
ensureOptimizations = false,
): number[] {
try {
const ast = getRegExpAst(regExp);
const firstChars = firstCharOptimizedIndices(
ast.value,
{},
ast.flags.ignoreCase,
);
return firstChars;
} catch (e) {
/* istanbul ignore next */
// Testing this relies on the regexp-to-ast library having a bug... */
// TODO: only the else branch needs to be ignored, try to fix with newer prettier / tsc
if (e.message === complementErrorMessage) {
if (ensureOptimizations) {
PRINT_WARNING(
`${failedOptimizationPrefixMsg}` +
`\tUnable to optimize: < ${regExp.toString()} >\n` +
"\tComplement Sets cannot be automatically optimized.\n" +
"\tThis will disable the lexer's first char optimizations.\n" +
"\tSee: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.",
);
}
} else {
let msgSuffix = "";
if (ensureOptimizations) {
msgSuffix =
"\n\tThis will disable the lexer's first char optimizations.\n" +
"\tSee: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.";
}
PRINT_ERROR(
`${failedOptimizationPrefixMsg}\n` +
`\tFailed parsing: < ${regExp.toString()} >\n` +
`\tUsing the @chevrotain/regexp-to-ast library\n` +
"\tPlease open an issue at: https://github.com/chevrotain/chevrotain/issues" +
msgSuffix,
);
}
}
return [];
}
export function firstCharOptimizedIndices(
ast: ASTNode,
result: { [charCode: number]: number },
ignoreCase: boolean,
): number[] {
switch (ast.type) {
case "Disjunction":
for (let i = 0; i < ast.value.length; i++) {
firstCharOptimizedIndices(ast.value[i], result, ignoreCase);
}
break;
case "Alternative":
const terms = ast.value;
for (let i = 0; i < terms.length; i++) {
const term = terms[i];
// skip terms that cannot effect the first char results
switch (term.type) {
case "EndAnchor":
// A group back reference cannot affect potential starting char.
// because if a back reference is the first production than automatically
// the group being referenced has had to come BEFORE so its codes have already been added
case "GroupBackReference":
// assertions do not affect potential starting codes
case "Lookahead":
case "NegativeLookahead":
case "Lookbehind":
case "NegativeLookbehind":
case "StartAnchor":
case "WordBoundary":
case "NonWordBoundary":
continue;
}
const atom = term;
switch (atom.type) {
case "Character":
addOptimizedIdxToResult(atom.value, result, ignoreCase);
break;
case "Set":
if (atom.complement === true) {
throw Error(complementErrorMessage);
}
forEach(atom.value, (code) => {
if (typeof code === "number") {
addOptimizedIdxToResult(code, result, ignoreCase);
} else {
// range
const range = code as any;
// cannot optimize when ignoreCase is
if (ignoreCase === true) {
for (
let rangeCode = range.from;
rangeCode <= range.to;
rangeCode++
) {
addOptimizedIdxToResult(rangeCode, result, ignoreCase);
}
}
// Optimization (2 orders of magnitude less work for very large ranges)
else {
// handle unoptimized values
for (
let rangeCode = range.from;
rangeCode <= range.to && rangeCode < minOptimizationVal;
rangeCode++
) {
addOptimizedIdxToResult(rangeCode, result, ignoreCase);
}
// Less common charCode where we optimize for faster init time, by using larger "buckets"
if (range.to >= minOptimizationVal) {
const minUnOptVal =
range.from >= minOptimizationVal
? range.from
: minOptimizationVal;
const maxUnOptVal = range.to;
const minOptIdx = charCodeToOptimizedIndex(minUnOptVal);
const maxOptIdx = charCodeToOptimizedIndex(maxUnOptVal);
for (
let currOptIdx = minOptIdx;
currOptIdx <= maxOptIdx;
currOptIdx++
) {
result[currOptIdx] = currOptIdx;
}
}
}
}
});
break;
case "Group":
firstCharOptimizedIndices(atom.value, result, ignoreCase);
break;
/* istanbul ignore next */
default:
throw Error("Non Exhaustive Match");
}
// reached a mandatory production, no more **start** codes can be found on this alternative
const isOptionalQuantifier =
atom.quantifier !== undefined && atom.quantifier.atLeast === 0;
if (
// A group may be optional due to empty contents /(?:)/
// or if everything inside it is optional /((a)?)/
(atom.type === "Group" && isWholeOptional(atom) === false) ||
// If this term is not a group it may only be optional if it has an optional quantifier
(atom.type !== "Group" && isOptionalQuantifier === false)
) {
break;
}
}
break;
/* istanbul ignore next */
default:
throw Error("non exhaustive match!");
}
// console.log(Object.keys(result).length)
return values(result);
}
function addOptimizedIdxToResult(
code: number,
result: { [charCode: number]: number },
ignoreCase: boolean,
) {
const optimizedCharIdx = charCodeToOptimizedIndex(code);
result[optimizedCharIdx] = optimizedCharIdx;
if (ignoreCase === true) {
handleIgnoreCase(code, result);
}
}
function handleIgnoreCase(
code: number,
result: { [charCode: number]: number },
) {
const char = String.fromCharCode(code);
const upperChar = char.toUpperCase();
/* istanbul ignore else */
if (upperChar !== char) {
const optimizedCharIdx = charCodeToOptimizedIndex(upperChar.charCodeAt(0));
result[optimizedCharIdx] = optimizedCharIdx;
} else {
const lowerChar = char.toLowerCase();
if (lowerChar !== char) {
const optimizedCharIdx = charCodeToOptimizedIndex(
lowerChar.charCodeAt(0),
);
result[optimizedCharIdx] = optimizedCharIdx;
}
}
}
function findCode(setNode: Set, targetCharCodes: number[]) {
return find(setNode.value, (codeOrRange) => {
if (typeof codeOrRange === "number") {
return includes(targetCharCodes, codeOrRange);
} else {
// range
const range = <any>codeOrRange;
return (
find(
targetCharCodes,
(targetCode) => range.from <= targetCode && targetCode <= range.to,
) !== undefined
);
}
});
}
function isWholeOptional(ast: any): boolean {
const quantifier = (ast as Atom).quantifier;
if (quantifier && quantifier.atLeast === 0) {
return true;
}
if (!ast.value) {
return false;
}
return isArray(ast.value)
? every(ast.value, isWholeOptional)
: isWholeOptional(ast.value);
}
class CharCodeFinder extends BaseRegExpVisitor {
found: boolean = false;
constructor(private targetCharCodes: number[]) {
super();
}
visitChildren(node: ASTNode) {
// No need to keep looking...
if (this.found === true) {
return;
}
// switch lookaheads / lookbehinds as they do not actually consume any characters thus
// finding a charCode at lookahead context does not mean that regexp can actually contain it in a match.
switch (node.type) {
case "Lookahead":
this.visitLookahead(node);
return;
case "NegativeLookahead":
this.visitNegativeLookahead(node);
return;
case "Lookbehind":
this.visitLookbehind(node);
return;
case "NegativeLookbehind":
this.visitNegativeLookbehind(node);
return;
}
super.visitChildren(node);
}
visitCharacter(node: Character) {
if (includes(this.targetCharCodes, node.value)) {
this.found = true;
}
}
visitSet(node: Set) {
if (node.complement) {
if (findCode(node, this.targetCharCodes) === undefined) {
this.found = true;
}
} else {
if (findCode(node, this.targetCharCodes) !== undefined) {
this.found = true;
}
}
}
}
export function canMatchCharCode(
charCodes: number[],
pattern: RegExp | string,
) {
if (pattern instanceof RegExp) {
const ast = getRegExpAst(pattern);
const charCodeFinder = new CharCodeFinder(charCodes);
charCodeFinder.visit(ast);
return charCodeFinder.found;
} else {
return (
find(<any>pattern, (char) => {
return includes(charCodes, (<string>char).charCodeAt(0));
}) !== undefined
);
}
}

View File

@@ -0,0 +1 @@
export const EOF_TOKEN_TYPE = 1;