chevrotain
Version:
Chevrotain is a high performance fault tolerant javascript parsing DSL for building recursive decent parsers
855 lines (783 loc) • 27.2 kB
text/typescript
import {
AtLeastOneSepMethodOpts,
ConsumeMethodOpts,
DSLMethodOpts,
DSLMethodOptsWithErr,
GrammarAction,
IOrAlt,
IParserConfig,
IRuleConfig,
IToken,
ManySepMethodOpts,
OrMethodOpts,
SubruleMethodOpts,
TokenType,
TokenVocabulary
} from "../../../../api"
import {
cloneArr,
cloneObj,
every,
flatten,
has,
isArray,
isEmpty,
isObject,
reduce,
uniq,
values
} from "../../../utils/utils"
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"
import {
isRecognitionException,
MismatchedTokenException,
NotAllInputParsedException
} from "../../exceptions_public"
import { PROD_TYPE } from "../../grammar/lookahead"
import {
AbstractNextTerminalAfterProductionWalker,
NextTerminalAfterAtLeastOneSepWalker,
NextTerminalAfterAtLeastOneWalker,
NextTerminalAfterManySepWalker,
NextTerminalAfterManyWalker
} from "../../grammar/interpreter"
import { DEFAULT_RULE_CONFIG, IParserState, TokenMatcher } from "../parser"
import { IN_RULE_RECOVERY_EXCEPTION } from "./recoverable"
import { EOF } from "../../../scan/tokens_public"
import { MixedInParser } from "./parser_traits"
import {
augmentTokenTypes,
isTokenType,
tokenStructuredMatcher,
tokenStructuredMatcherNoCategories
} from "../../../scan/tokens"
import { classNameFromInstance } from "../../../lang/lang_extensions"
import { Rule } from "../../grammar/gast/gast_public"
/**
* This trait is responsible for the runtime parsing engine
* Used by the official API (recognizer_api.ts)
*/
export class RecognizerEngine {
isBackTrackingStack
className: string
RULE_STACK: string[]
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
initRecognizerEngine(
tokenVocabulary: TokenVocabulary,
config: IParserConfig
) {
this.className = classNameFromInstance(this)
// 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.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 = <any>reduce(
<any>tokenVocabulary,
(acc, tokType: TokenType) => {
acc[tokType.name] = tokType
return acc
},
{}
)
} else if (
has(tokenVocabulary, "modes") &&
every(flatten(values((<any>tokenVocabulary).modes)), isTokenType)
) {
let allTokenTypes = flatten(values((<any>tokenVocabulary).modes))
let uniqueTokens = uniq(allTokenTypes)
this.tokensMap = <any>reduce(
uniqueTokens,
(acc, tokType: TokenType) => {
acc[tokType.name] = tokType
return acc
},
{}
)
} else if (isObject(tokenVocabulary)) {
this.tokensMap = cloneObj(tokenVocabulary)
} 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 ...")
/* tslint:disable */
this.tokensMap["EOF"] = EOF
// TODO: This check may not be accurate for multi mode lexers
const noTokenCategoriesUsed = every(
values(tokenVocabulary),
(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<T>(
this: MixedInParser,
ruleName: string,
impl: (...implArgs: any[]) => T,
config: IRuleConfig<T>
): (idxInCallingRule?: number, ...args: any[]) => T {
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.`
)
}
let resyncEnabled = has(config, "resyncEnabled")
? config.resyncEnabled
: DEFAULT_RULE_CONFIG.resyncEnabled
let recoveryValueFunc = has(config, "recoveryValueFunc")
? config.recoveryValueFunc
: 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).
/* tslint:disable */
let shortName =
this.ruleShortNameIdx << (BITS_FOR_METHOD_TYPE + BITS_FOR_OCCURRENCE_IDX)
/* tslint:enable */
this.ruleShortNameIdx++
this.shortRuleNameToFull[shortName] = ruleName
this.fullRuleNameToShort[ruleName] = shortName
function invokeRuleWithTry(args: any[]) {
try {
if (this.outputCst === true) {
impl.apply(this, args)
const cst = this.CST_STACK[this.CST_STACK.length - 1]
this.cstPostRule(cst)
return cst
} else {
return impl.apply(this, args)
}
} catch (e) {
return this.invokeRuleCatch(e, resyncEnabled, recoveryValueFunc)
} finally {
this.ruleFinallyStateUpdate()
}
}
let wrappedGrammarRule
wrappedGrammarRule = function (idxInCallingRule: number = 0, args: any[]) {
this.ruleInvocationStateUpdate(shortName, ruleName, idxInCallingRule)
return invokeRuleWithTry.call(this, args)
}
let ruleNamePropName = "ruleName"
wrappedGrammarRule[ruleNamePropName] = ruleName
wrappedGrammarRule["originalGrammarAction"] = impl
return wrappedGrammarRule
}
invokeRuleCatch(
this: MixedInParser,
e: Error,
resyncEnabledConfig: boolean,
recoveryValueFunc: Function
): void {
let 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
let reSyncEnabled =
resyncEnabledConfig && !this.isBackTracking() && this.recoveryEnabled
if (isRecognitionException(e)) {
const recogError: any = e
if (reSyncEnabled) {
let reSyncTokType = this.findReSyncTokenType()
if (this.isInCurrentRuleReSyncSet(reSyncTokType)) {
recogError.resyncedTokens = this.reSyncTo(reSyncTokType)
if (this.outputCst) {
let partialCstResult: any = this.CST_STACK[
this.CST_STACK.length - 1
]
partialCstResult.recoveredNode = true
return partialCstResult
} else {
return recoveryValueFunc()
}
} 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()
} 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 {
let 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 {
let lookAheadFunc = this.getLaFuncFromCache(key)
let action
let predicate
if ((<DSLMethodOpts<OUT>>actionORMethodDef).DEF !== undefined) {
action = (<DSLMethodOpts<OUT>>actionORMethodDef).DEF
predicate = (<DSLMethodOpts<OUT>>actionORMethodDef).GATE
// predicate present
if (predicate !== undefined) {
let 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 {
let 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
let predicate
if ((<DSLMethodOptsWithErr<OUT>>actionORMethodDef).DEF !== undefined) {
action = (<DSLMethodOptsWithErr<OUT>>actionORMethodDef).DEF
predicate = (<DSLMethodOptsWithErr<OUT>>actionORMethodDef).GATE
// predicate present
if (predicate !== undefined) {
let 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 {
let 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 {
let action = options.DEF
let separator = options.SEP
let 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.
let 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 {
let 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
let predicate
if ((<DSLMethodOpts<OUT>>actionORMethodDef).DEF !== undefined) {
action = (<DSLMethodOpts<OUT>>actionORMethodDef).DEF
predicate = (<DSLMethodOpts<OUT>>actionORMethodDef).GATE
// predicate present
if (predicate !== undefined) {
let 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 {
let laKey = this.getKeyForAutomaticLookahead(MANY_SEP_IDX, prodOccurrence)
this.manySepFirstInternalLogic(prodOccurrence, options, laKey)
}
manySepFirstInternalLogic<OUT>(
this: MixedInParser,
prodOccurrence: number,
options: ManySepMethodOpts<OUT>,
key: number
): void {
let action = options.DEF
let separator = options.SEP
let firstIterationLaFunc = this.getLaFuncFromCache(key)
// 1st iteration
if (firstIterationLaFunc.call(this) === true) {
action.call(this)
let 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 {
let laKey = this.getKeyForAutomaticLookahead(OR_IDX, occurrence)
let alts = isArray(altsOrOpts)
? (altsOrOpts as IOrAlt<any>[])
: (altsOrOpts as OrMethodOpts<unknown>).DEF
const laFunc = this.getLaFuncFromCache(laKey)
let altIdxToTake = laFunc.call(this, alts)
if (altIdxToTake !== undefined) {
let 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) {
let firstRedundantTok = this.LA(1)
let errMsg = this.errorMessageProvider.buildNotAllInputParsedMessage({
firstRedundant: firstRedundantTok,
ruleName: this.getCurrRuleFullName()
})
this.SAVE_ERROR(new NotAllInputParsedException(errMsg, firstRedundantTok))
}
}
subruleInternal<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
idx: number,
options?: SubruleMethodOpts
) {
let ruleResult
try {
const args = options !== undefined ? options.ARGS : undefined
ruleResult = ruleToCall.call(this, idx, args)
this.cstPostNonTerminal(
ruleResult,
options !== undefined && options.LABEL !== undefined
? options.LABEL
: (<any>ruleToCall).ruleName
)
return ruleResult
} catch (e) {
this.subruleInternalError(e, options, (<any>ruleToCall).ruleName)
}
}
subruleInternalError(
this: MixedInParser,
e: any,
options: SubruleMethodOpts,
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
): IToken {
let consumedToken
try {
let 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
): void {
let msg
let 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()
) {
let 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
let savedErrors = this.errors
let savedRuleStack = cloneArr(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: string,
fullName: string,
idxInCallingRule: number
): void {
this.RULE_OCCURRENCE_STACK.push(idxInCallingRule)
this.RULE_STACK.push(shortName)
// NOOP when cst is disabled
this.cstInvocationStateUpdate(fullName, shortName)
}
isBackTracking(this: MixedInParser): boolean {
return this.isBackTrackingStack.length !== 0
}
getCurrRuleFullName(this: MixedInParser): string {
let shortName = this.getLastExplicitRuleShortName()
return this.shortRuleNameToFull[shortName]
}
shortRuleNameToFullName(this: MixedInParser, shortName: string) {
return this.shortRuleNameToFull[shortName]
}
public isAtEndOfInput(this: MixedInParser): boolean {
return this.tokenMatcher(this.LA(1), EOF)
}
public reset(this: MixedInParser): void {
this.resetLexerState()
this.isBackTrackingStack = []
this.errors = []
this.RULE_STACK = []
// TODO: extract a specific reset for TreeBuilder trait
this.CST_STACK = []
this.RULE_OCCURRENCE_STACK = []
}
}