chevrotain
Version:
Chevrotain is a high performance fault tolerant javascript parsing DSL for building recursive decent parsers
868 lines (799 loc) • 28.3 kB
text/typescript
import {
AtLeastOneSepMethodOpts,
ConsumeMethodOpts,
DSLMethodOpts,
DSLMethodOptsWithErr,
GrammarAction,
IOrAlt,
IParserConfig,
IRuleConfig,
IToken,
ManySepMethodOpts,
OrMethodOpts,
ParserMethod,
SubruleMethodOpts,
TokenType,
TokenTypeDictionary,
TokenVocabulary,
} from "@chevrotain/types";
import {
clone,
every,
flatten,
has,
isArray,
isEmpty,
isObject,
reduce,
uniq,
values,
} from "lodash-es";
import {
AT_LEAST_ONE_IDX,
AT_LEAST_ONE_SEP_IDX,
BITS_FOR_METHOD_TYPE,
BITS_FOR_OCCURRENCE_IDX,
MANY_IDX,
MANY_SEP_IDX,
OPTION_IDX,
OR_IDX,
} from "../../grammar/keys.js";
import {
isRecognitionException,
MismatchedTokenException,
NotAllInputParsedException,
} from "../../exceptions_public.js";
import { PROD_TYPE } from "../../grammar/lookahead.js";
import {
AbstractNextTerminalAfterProductionWalker,
NextTerminalAfterAtLeastOneSepWalker,
NextTerminalAfterAtLeastOneWalker,
NextTerminalAfterManySepWalker,
NextTerminalAfterManyWalker,
} from "../../grammar/interpreter.js";
import { DEFAULT_RULE_CONFIG, IParserState, TokenMatcher } from "../parser.js";
import { IN_RULE_RECOVERY_EXCEPTION } from "./recoverable.js";
import { EOF } from "../../../scan/tokens_public.js";
import { MixedInParser } from "./parser_traits.js";
import {
augmentTokenTypes,
isTokenType,
tokenStructuredMatcher,
tokenStructuredMatcherNoCategories,
} from "../../../scan/tokens.js";
import { Rule } from "@chevrotain/gast";
import { ParserMethodInternal } from "../types.js";
/**
* This trait is responsible for the runtime parsing engine
* Used by the official API (recognizer_api.ts)
*/
export class RecognizerEngine {
isBackTrackingStack: boolean[];
className: string;
RULE_STACK: number[];
RULE_OCCURRENCE_STACK: number[];
definedRulesNames: string[];
tokensMap: { [fqn: string]: TokenType };
gastProductionsCache: Record<string, Rule>;
shortRuleNameToFull: Record<string, string>;
fullRuleNameToShort: Record<string, number>;
// The shortName Index must be coded "after" the first 8bits to enable building unique lookahead keys
ruleShortNameIdx: number;
tokenMatcher: TokenMatcher;
subruleIdx: number;
initRecognizerEngine(
tokenVocabulary: TokenVocabulary,
config: IParserConfig,
) {
this.className = this.constructor.name;
// TODO: would using an ES6 Map or plain object be faster (CST building scenario)
this.shortRuleNameToFull = {};
this.fullRuleNameToShort = {};
this.ruleShortNameIdx = 256;
this.tokenMatcher = tokenStructuredMatcherNoCategories;
this.subruleIdx = 0;
this.definedRulesNames = [];
this.tokensMap = {};
this.isBackTrackingStack = [];
this.RULE_STACK = [];
this.RULE_OCCURRENCE_STACK = [];
this.gastProductionsCache = {};
if (has(config, "serializedGrammar")) {
throw Error(
"The Parser's configuration can no longer contain a <serializedGrammar> property.\n" +
"\tSee: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0\n" +
"\tFor Further details.",
);
}
if (isArray(tokenVocabulary)) {
// This only checks for Token vocabularies provided as arrays.
// That is good enough because the main objective is to detect users of pre-V4.0 APIs
// rather than all edge cases of empty Token vocabularies.
if (isEmpty(tokenVocabulary as any[])) {
throw Error(
"A Token Vocabulary cannot be empty.\n" +
"\tNote that the first argument for the parser constructor\n" +
"\tis no longer a Token vector (since v4.0).",
);
}
if (typeof (tokenVocabulary as any[])[0].startOffset === "number") {
throw Error(
"The Parser constructor no longer accepts a token vector as the first argument.\n" +
"\tSee: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0\n" +
"\tFor Further details.",
);
}
}
if (isArray(tokenVocabulary)) {
this.tokensMap = reduce(
tokenVocabulary,
(acc, tokType: TokenType) => {
acc[tokType.name] = tokType;
return acc;
},
{} as { [tokenName: string]: TokenType },
);
} else if (
has(tokenVocabulary, "modes") &&
every(flatten(values((<any>tokenVocabulary).modes)), isTokenType)
) {
const allTokenTypes = flatten(values((<any>tokenVocabulary).modes));
const uniqueTokens = uniq(allTokenTypes);
this.tokensMap = <any>reduce(
uniqueTokens,
(acc, tokType: TokenType) => {
acc[tokType.name] = tokType;
return acc;
},
{} as { [tokenName: string]: TokenType },
);
} else if (isObject(tokenVocabulary)) {
this.tokensMap = clone(tokenVocabulary as TokenTypeDictionary);
} else {
throw new Error(
"<tokensDictionary> argument must be An Array of Token constructors," +
" A dictionary of Token constructors or an IMultiModeLexerDefinition",
);
}
// always add EOF to the tokenNames -> constructors map. it is useful to assure all the input has been
// parsed with a clear error message ("expecting EOF but found ...")
this.tokensMap["EOF"] = EOF;
const allTokenTypes = has(tokenVocabulary, "modes")
? flatten(values((<any>tokenVocabulary).modes))
: values(tokenVocabulary);
const noTokenCategoriesUsed = every(allTokenTypes, (tokenConstructor) =>
isEmpty(tokenConstructor.categoryMatches),
);
this.tokenMatcher = noTokenCategoriesUsed
? tokenStructuredMatcherNoCategories
: tokenStructuredMatcher;
// Because ES2015+ syntax should be supported for creating Token classes
// We cannot assume that the Token classes were created using the "extendToken" utilities
// Therefore we must augment the Token classes both on Lexer initialization and on Parser initialization
augmentTokenTypes(values(this.tokensMap));
}
defineRule<ARGS extends unknown[], R>(
this: MixedInParser,
ruleName: string,
impl: (...args: ARGS) => R,
config: IRuleConfig<R>,
): ParserMethodInternal<ARGS, R> {
if (this.selfAnalysisDone) {
throw Error(
`Grammar rule <${ruleName}> may not be defined after the 'performSelfAnalysis' method has been called'\n` +
`Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`,
);
}
const resyncEnabled: boolean = has(config, "resyncEnabled")
? (config.resyncEnabled as boolean) // assumes end user provides the correct config value/type
: DEFAULT_RULE_CONFIG.resyncEnabled;
const recoveryValueFunc = has(config, "recoveryValueFunc")
? (config.recoveryValueFunc as () => R) // assumes end user provides the correct config value/type
: DEFAULT_RULE_CONFIG.recoveryValueFunc;
// performance optimization: Use small integers as keys for the longer human readable "full" rule names.
// this greatly improves Map access time (as much as 8% for some performance benchmarks).
const shortName =
this.ruleShortNameIdx << (BITS_FOR_METHOD_TYPE + BITS_FOR_OCCURRENCE_IDX);
this.ruleShortNameIdx++;
this.shortRuleNameToFull[shortName] = ruleName;
this.fullRuleNameToShort[ruleName] = shortName;
let invokeRuleWithTry: ParserMethod<ARGS, R>;
// Micro optimization, only check the condition **once** on rule definition
// instead of **every single** rule invocation.
if (this.outputCst === true) {
invokeRuleWithTry = function invokeRuleWithTry(
this: MixedInParser,
...args: ARGS
): R {
try {
this.ruleInvocationStateUpdate(shortName, ruleName, this.subruleIdx);
impl.apply(this, args);
const cst = this.CST_STACK[this.CST_STACK.length - 1];
this.cstPostRule(cst);
return cst as unknown as R;
} catch (e) {
return this.invokeRuleCatch(e, resyncEnabled, recoveryValueFunc) as R;
} finally {
this.ruleFinallyStateUpdate();
}
};
} else {
invokeRuleWithTry = function invokeRuleWithTryCst(
this: MixedInParser,
...args: ARGS
): R {
try {
this.ruleInvocationStateUpdate(shortName, ruleName, this.subruleIdx);
return impl.apply(this, args);
} catch (e) {
return this.invokeRuleCatch(e, resyncEnabled, recoveryValueFunc) as R;
} finally {
this.ruleFinallyStateUpdate();
}
};
}
const wrappedGrammarRule: ParserMethodInternal<ARGS, R> = Object.assign(
invokeRuleWithTry as any,
{ ruleName, originalGrammarAction: impl },
);
return wrappedGrammarRule;
}
invokeRuleCatch(
this: MixedInParser,
e: Error,
resyncEnabledConfig: boolean,
recoveryValueFunc: Function,
): unknown {
const isFirstInvokedRule = this.RULE_STACK.length === 1;
// note the reSync is always enabled for the first rule invocation, because we must always be able to
// reSync with EOF and just output some INVALID ParseTree
// during backtracking reSync recovery is disabled, otherwise we can't be certain the backtracking
// path is really the most valid one
const reSyncEnabled =
resyncEnabledConfig && !this.isBackTracking() && this.recoveryEnabled;
if (isRecognitionException(e)) {
const recogError: any = e;
if (reSyncEnabled) {
const reSyncTokType = this.findReSyncTokenType();
if (this.isInCurrentRuleReSyncSet(reSyncTokType)) {
recogError.resyncedTokens = this.reSyncTo(reSyncTokType);
if (this.outputCst) {
const partialCstResult: any =
this.CST_STACK[this.CST_STACK.length - 1];
partialCstResult.recoveredNode = true;
return partialCstResult;
} else {
return recoveryValueFunc(e);
}
} else {
if (this.outputCst) {
const partialCstResult: any =
this.CST_STACK[this.CST_STACK.length - 1];
partialCstResult.recoveredNode = true;
recogError.partialCstResult = partialCstResult;
}
// to be handled Further up the call stack
throw recogError;
}
} else if (isFirstInvokedRule) {
// otherwise a Redundant input error will be created as well and we cannot guarantee that this is indeed the case
this.moveToTerminatedState();
// the parser should never throw one of its own errors outside its flow.
// even if error recovery is disabled
return recoveryValueFunc(e);
} else {
// to be recovered Further up the call stack
throw recogError;
}
} else {
// some other Error type which we don't know how to handle (for example a built in JavaScript Error)
throw e;
}
}
// Implementation of parsing DSL
optionInternal<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
occurrence: number,
): OUT | undefined {
const key = this.getKeyForAutomaticLookahead(OPTION_IDX, occurrence);
return this.optionInternalLogic(actionORMethodDef, occurrence, key);
}
optionInternalLogic<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
occurrence: number,
key: number,
): OUT | undefined {
let lookAheadFunc = this.getLaFuncFromCache(key);
let action: GrammarAction<OUT>;
if (typeof actionORMethodDef !== "function") {
action = actionORMethodDef.DEF;
const predicate = actionORMethodDef.GATE;
// predicate present
if (predicate !== undefined) {
const orgLookaheadFunction = lookAheadFunc;
lookAheadFunc = () => {
return predicate.call(this) && orgLookaheadFunction.call(this);
};
}
} else {
action = actionORMethodDef;
}
if (lookAheadFunc.call(this) === true) {
return action.call(this);
}
return undefined;
}
atLeastOneInternal<OUT>(
this: MixedInParser,
prodOccurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
): void {
const laKey = this.getKeyForAutomaticLookahead(
AT_LEAST_ONE_IDX,
prodOccurrence,
);
return this.atLeastOneInternalLogic(
prodOccurrence,
actionORMethodDef,
laKey,
);
}
atLeastOneInternalLogic<OUT>(
this: MixedInParser,
prodOccurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>,
key: number,
): void {
let lookAheadFunc = this.getLaFuncFromCache(key);
let action;
if (typeof actionORMethodDef !== "function") {
action = actionORMethodDef.DEF;
const predicate = actionORMethodDef.GATE;
// predicate present
if (predicate !== undefined) {
const orgLookaheadFunction = lookAheadFunc;
lookAheadFunc = () => {
return predicate.call(this) && orgLookaheadFunction.call(this);
};
}
} else {
action = actionORMethodDef;
}
if ((<Function>lookAheadFunc).call(this) === true) {
let notStuck = this.doSingleRepetition(action);
while (
(<Function>lookAheadFunc).call(this) === true &&
notStuck === true
) {
notStuck = this.doSingleRepetition(action);
}
} else {
throw this.raiseEarlyExitException(
prodOccurrence,
PROD_TYPE.REPETITION_MANDATORY,
(<DSLMethodOptsWithErr<OUT>>actionORMethodDef).ERR_MSG,
);
}
// note that while it may seem that this can cause an error because by using a recursive call to
// AT_LEAST_ONE we change the grammar to AT_LEAST_TWO, AT_LEAST_THREE ... , the possible recursive call
// from the tryInRepetitionRecovery(...) will only happen IFF there really are TWO/THREE/.... items.
// Performance optimization: "attemptInRepetitionRecovery" will be defined as NOOP unless recovery is enabled
this.attemptInRepetitionRecovery(
this.atLeastOneInternal,
[prodOccurrence, actionORMethodDef],
<any>lookAheadFunc,
AT_LEAST_ONE_IDX,
prodOccurrence,
NextTerminalAfterAtLeastOneWalker,
);
}
atLeastOneSepFirstInternal<OUT>(
this: MixedInParser,
prodOccurrence: number,
options: AtLeastOneSepMethodOpts<OUT>,
): void {
const laKey = this.getKeyForAutomaticLookahead(
AT_LEAST_ONE_SEP_IDX,
prodOccurrence,
);
this.atLeastOneSepFirstInternalLogic(prodOccurrence, options, laKey);
}
atLeastOneSepFirstInternalLogic<OUT>(
this: MixedInParser,
prodOccurrence: number,
options: AtLeastOneSepMethodOpts<OUT>,
key: number,
): void {
const action = options.DEF;
const separator = options.SEP;
const firstIterationLookaheadFunc = this.getLaFuncFromCache(key);
// 1st iteration
if (firstIterationLookaheadFunc.call(this) === true) {
(<GrammarAction<OUT>>action).call(this);
// TODO: Optimization can move this function construction into "attemptInRepetitionRecovery"
// because it is only needed in error recovery scenarios.
const separatorLookAheadFunc = () => {
return this.tokenMatcher(this.LA(1), separator);
};
// 2nd..nth iterations
while (this.tokenMatcher(this.LA(1), separator) === true) {
// note that this CONSUME will never enter recovery because
// the separatorLookAheadFunc checks that the separator really does exist.
this.CONSUME(separator);
// No need for checking infinite loop here due to consuming the separator.
(<GrammarAction<OUT>>action).call(this);
}
// Performance optimization: "attemptInRepetitionRecovery" will be defined as NOOP unless recovery is enabled
this.attemptInRepetitionRecovery(
this.repetitionSepSecondInternal,
[
prodOccurrence,
separator,
separatorLookAheadFunc,
action,
NextTerminalAfterAtLeastOneSepWalker,
],
separatorLookAheadFunc,
AT_LEAST_ONE_SEP_IDX,
prodOccurrence,
NextTerminalAfterAtLeastOneSepWalker,
);
} else {
throw this.raiseEarlyExitException(
prodOccurrence,
PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,
options.ERR_MSG,
);
}
}
manyInternal<OUT>(
this: MixedInParser,
prodOccurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
): void {
const laKey = this.getKeyForAutomaticLookahead(MANY_IDX, prodOccurrence);
return this.manyInternalLogic(prodOccurrence, actionORMethodDef, laKey);
}
manyInternalLogic<OUT>(
this: MixedInParser,
prodOccurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
key: number,
) {
let lookaheadFunction = this.getLaFuncFromCache(key);
let action;
if (typeof actionORMethodDef !== "function") {
action = actionORMethodDef.DEF;
const predicate = actionORMethodDef.GATE;
// predicate present
if (predicate !== undefined) {
const orgLookaheadFunction = lookaheadFunction;
lookaheadFunction = () => {
return predicate.call(this) && orgLookaheadFunction.call(this);
};
}
} else {
action = actionORMethodDef;
}
let notStuck = true;
while (lookaheadFunction.call(this) === true && notStuck === true) {
notStuck = this.doSingleRepetition(action);
}
// Performance optimization: "attemptInRepetitionRecovery" will be defined as NOOP unless recovery is enabled
this.attemptInRepetitionRecovery(
this.manyInternal,
[prodOccurrence, actionORMethodDef],
<any>lookaheadFunction,
MANY_IDX,
prodOccurrence,
NextTerminalAfterManyWalker,
// The notStuck parameter is only relevant when "attemptInRepetitionRecovery"
// is invoked from manyInternal, in the MANY_SEP case and AT_LEAST_ONE[_SEP]
// An infinite loop cannot occur as:
// - Either the lookahead is guaranteed to consume something (Single Token Separator)
// - AT_LEAST_ONE by definition is guaranteed to consume something (or error out).
notStuck,
);
}
manySepFirstInternal<OUT>(
this: MixedInParser,
prodOccurrence: number,
options: ManySepMethodOpts<OUT>,
): void {
const laKey = this.getKeyForAutomaticLookahead(
MANY_SEP_IDX,
prodOccurrence,
);
this.manySepFirstInternalLogic(prodOccurrence, options, laKey);
}
manySepFirstInternalLogic<OUT>(
this: MixedInParser,
prodOccurrence: number,
options: ManySepMethodOpts<OUT>,
key: number,
): void {
const action = options.DEF;
const separator = options.SEP;
const firstIterationLaFunc = this.getLaFuncFromCache(key);
// 1st iteration
if (firstIterationLaFunc.call(this) === true) {
action.call(this);
const separatorLookAheadFunc = () => {
return this.tokenMatcher(this.LA(1), separator);
};
// 2nd..nth iterations
while (this.tokenMatcher(this.LA(1), separator) === true) {
// note that this CONSUME will never enter recovery because
// the separatorLookAheadFunc checks that the separator really does exist.
this.CONSUME(separator);
// No need for checking infinite loop here due to consuming the separator.
action.call(this);
}
// Performance optimization: "attemptInRepetitionRecovery" will be defined as NOOP unless recovery is enabled
this.attemptInRepetitionRecovery(
this.repetitionSepSecondInternal,
[
prodOccurrence,
separator,
separatorLookAheadFunc,
action,
NextTerminalAfterManySepWalker,
],
separatorLookAheadFunc,
MANY_SEP_IDX,
prodOccurrence,
NextTerminalAfterManySepWalker,
);
}
}
repetitionSepSecondInternal<OUT>(
this: MixedInParser,
prodOccurrence: number,
separator: TokenType,
separatorLookAheadFunc: () => boolean,
action: GrammarAction<OUT>,
nextTerminalAfterWalker: typeof AbstractNextTerminalAfterProductionWalker,
): void {
while (separatorLookAheadFunc()) {
// note that this CONSUME will never enter recovery because
// the separatorLookAheadFunc checks that the separator really does exist.
this.CONSUME(separator);
action.call(this);
}
// we can only arrive to this function after an error
// has occurred (hence the name 'second') so the following
// IF will always be entered, its possible to remove it...
// however it is kept to avoid confusion and be consistent.
// Performance optimization: "attemptInRepetitionRecovery" will be defined as NOOP unless recovery is enabled
/* istanbul ignore else */
this.attemptInRepetitionRecovery(
this.repetitionSepSecondInternal,
[
prodOccurrence,
separator,
separatorLookAheadFunc,
action,
nextTerminalAfterWalker,
],
separatorLookAheadFunc,
AT_LEAST_ONE_SEP_IDX,
prodOccurrence,
nextTerminalAfterWalker,
);
}
doSingleRepetition(this: MixedInParser, action: Function): any {
const beforeIteration = this.getLexerPosition();
action.call(this);
const afterIteration = this.getLexerPosition();
// This boolean will indicate if this repetition progressed
// or if we are "stuck" (potential infinite loop in the repetition).
return afterIteration > beforeIteration;
}
orInternal<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
occurrence: number,
): T {
const laKey = this.getKeyForAutomaticLookahead(OR_IDX, occurrence);
const alts = isArray(altsOrOpts) ? altsOrOpts : altsOrOpts.DEF;
const laFunc = this.getLaFuncFromCache(laKey);
const altIdxToTake = laFunc.call(this, alts);
if (altIdxToTake !== undefined) {
const chosenAlternative: any = alts[altIdxToTake];
return chosenAlternative.ALT.call(this);
}
this.raiseNoAltException(
occurrence,
(altsOrOpts as OrMethodOpts<unknown>).ERR_MSG,
);
}
ruleFinallyStateUpdate(this: MixedInParser): void {
this.RULE_STACK.pop();
this.RULE_OCCURRENCE_STACK.pop();
// NOOP when cst is disabled
this.cstFinallyStateUpdate();
if (this.RULE_STACK.length === 0 && this.isAtEndOfInput() === false) {
const firstRedundantTok = this.LA(1);
const errMsg = this.errorMessageProvider.buildNotAllInputParsedMessage({
firstRedundant: firstRedundantTok,
ruleName: this.getCurrRuleFullName(),
});
this.SAVE_ERROR(
new NotAllInputParsedException(errMsg, firstRedundantTok),
);
}
}
subruleInternal<ARGS extends unknown[], R>(
this: MixedInParser,
ruleToCall: ParserMethodInternal<ARGS, R>,
idx: number,
options?: SubruleMethodOpts<ARGS>,
): R {
let ruleResult;
try {
const args = options !== undefined ? options.ARGS : undefined;
this.subruleIdx = idx;
ruleResult = ruleToCall.apply(this, args);
this.cstPostNonTerminal(
ruleResult,
options !== undefined && options.LABEL !== undefined
? options.LABEL
: ruleToCall.ruleName,
);
return ruleResult;
} catch (e) {
throw this.subruleInternalError(e, options, ruleToCall.ruleName);
}
}
subruleInternalError(
this: MixedInParser,
e: any,
options: SubruleMethodOpts<unknown[]> | undefined,
ruleName: string,
): void {
if (isRecognitionException(e) && e.partialCstResult !== undefined) {
this.cstPostNonTerminal(
e.partialCstResult,
options !== undefined && options.LABEL !== undefined
? options.LABEL
: ruleName,
);
delete e.partialCstResult;
}
throw e;
}
consumeInternal(
this: MixedInParser,
tokType: TokenType,
idx: number,
options: ConsumeMethodOpts | undefined,
): IToken {
let consumedToken!: IToken;
try {
const nextToken = this.LA(1);
if (this.tokenMatcher(nextToken, tokType) === true) {
this.consumeToken();
consumedToken = nextToken;
} else {
this.consumeInternalError(tokType, nextToken, options);
}
} catch (eFromConsumption) {
consumedToken = this.consumeInternalRecovery(
tokType,
idx,
eFromConsumption,
);
}
this.cstPostTerminal(
options !== undefined && options.LABEL !== undefined
? options.LABEL
: tokType.name,
consumedToken,
);
return consumedToken;
}
consumeInternalError(
this: MixedInParser,
tokType: TokenType,
nextToken: IToken,
options: ConsumeMethodOpts | undefined,
): void {
let msg;
const previousToken = this.LA(0);
if (options !== undefined && options.ERR_MSG) {
msg = options.ERR_MSG;
} else {
msg = this.errorMessageProvider.buildMismatchTokenMessage({
expected: tokType,
actual: nextToken,
previous: previousToken,
ruleName: this.getCurrRuleFullName(),
});
}
throw this.SAVE_ERROR(
new MismatchedTokenException(msg, nextToken, previousToken),
);
}
consumeInternalRecovery(
this: MixedInParser,
tokType: TokenType,
idx: number,
eFromConsumption: Error,
): IToken {
// no recovery allowed during backtracking, otherwise backtracking may recover invalid syntax and accept it
// but the original syntax could have been parsed successfully without any backtracking + recovery
if (
this.recoveryEnabled &&
// TODO: more robust checking of the exception type. Perhaps Typescript extending expressions?
eFromConsumption.name === "MismatchedTokenException" &&
!this.isBackTracking()
) {
const follows = this.getFollowsForInRuleRecovery(<any>tokType, idx);
try {
return this.tryInRuleRecovery(<any>tokType, follows);
} catch (eFromInRuleRecovery) {
if (eFromInRuleRecovery.name === IN_RULE_RECOVERY_EXCEPTION) {
// failed in RuleRecovery.
// throw the original error in order to trigger reSync error recovery
throw eFromConsumption;
} else {
throw eFromInRuleRecovery;
}
}
} else {
throw eFromConsumption;
}
}
saveRecogState(this: MixedInParser): IParserState {
// errors is a getter which will clone the errors array
const savedErrors = this.errors;
const savedRuleStack = clone(this.RULE_STACK);
return {
errors: savedErrors,
lexerState: this.exportLexerState(),
RULE_STACK: savedRuleStack,
CST_STACK: this.CST_STACK,
};
}
reloadRecogState(this: MixedInParser, newState: IParserState) {
this.errors = newState.errors;
this.importLexerState(newState.lexerState);
this.RULE_STACK = newState.RULE_STACK;
}
ruleInvocationStateUpdate(
this: MixedInParser,
shortName: number,
fullName: string,
idxInCallingRule: number,
): void {
this.RULE_OCCURRENCE_STACK.push(idxInCallingRule);
this.RULE_STACK.push(shortName);
// NOOP when cst is disabled
this.cstInvocationStateUpdate(fullName);
}
isBackTracking(this: MixedInParser): boolean {
return this.isBackTrackingStack.length !== 0;
}
getCurrRuleFullName(this: MixedInParser): string {
const shortName = this.getLastExplicitRuleShortName();
return this.shortRuleNameToFull[shortName];
}
shortRuleNameToFullName(this: MixedInParser, shortName: number) {
return this.shortRuleNameToFull[shortName];
}
public isAtEndOfInput(this: MixedInParser): boolean {
return this.tokenMatcher(this.LA(1), EOF);
}
public reset(this: MixedInParser): void {
this.resetLexerState();
this.subruleIdx = 0;
this.isBackTrackingStack = [];
this.errors = [];
this.RULE_STACK = [];
// TODO: extract a specific reset for TreeBuilder trait
this.CST_STACK = [];
this.RULE_OCCURRENCE_STACK = [];
}
}