Add capacitorjs runtime

This commit is contained in:
olcxja 2026-05-03 17:09:55 +02:00
commit f90c0e6c40
8362 changed files with 1502407 additions and 1 deletions

310
node_modules/chevrotain/src/parse/parser/parser.ts generated vendored Normal file
View file

@ -0,0 +1,310 @@
import {
applyMixins,
cloneObj,
forEach,
has,
isEmpty,
map,
toFastProperties,
values
} from "../../utils/utils"
import { computeAllProdsFollows } from "../grammar/follow"
import { createTokenInstance, EOF } from "../../scan/tokens_public"
import {
defaultGrammarValidatorErrorProvider,
defaultParserErrorProvider
} from "../errors_public"
import {
resolveGrammar,
validateGrammar
} from "../grammar/gast/gast_resolver_public"
import {
CstNode,
IParserConfig,
IParserDefinitionError,
IRecognitionException,
IRuleConfig,
IToken,
TokenType,
TokenVocabulary
} from "../../../api"
import { Recoverable } from "./traits/recoverable"
import { LooksAhead } from "./traits/looksahead"
import { TreeBuilder } from "./traits/tree_builder"
import { LexerAdapter } from "./traits/lexer_adapter"
import { RecognizerApi } from "./traits/recognizer_api"
import { RecognizerEngine } from "./traits/recognizer_engine"
import { ErrorHandler } from "./traits/error_handler"
import { MixedInParser } from "./traits/parser_traits"
import { ContentAssist } from "./traits/context_assist"
import { GastRecorder } from "./traits/gast_recorder"
import { PerformanceTracer } from "./traits/perf_tracer"
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 type lookAheadSequence = TokenType[][]
export const DEFAULT_PARSER_CONFIG: IParserConfig = Object.freeze({
recoveryEnabled: false,
maxLookahead: 3,
dynamicTokensEnabled: false,
outputCst: true,
errorMessageProvider: defaultParserErrorProvider,
nodeLocationTracking: "none",
traceInitPerf: false,
skipValidations: false
})
export const DEFAULT_RULE_CONFIG: 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
}
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
alternatives: number[]
}
export interface IParserUnresolvedRefDefinitionError
extends IParserDefinitionError {
unresolvedRefName: string
}
export interface IParserState {
errors: IRecognitionException[]
lexerState: any
RULE_STACK: string[]
CST_STACK: CstNode[]
}
export type Predicate = () => boolean
export function EMPTY_ALT<T>(value: T = undefined): () => T {
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, ect...
// 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
let 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[currRuleName]
const originalGrammarAction = wrappedRule["originalGrammarAction"]
let recordedRuleGast = undefined
this.TRACE_INIT(`${currRuleName} Rule`, () => {
recordedRuleGast = this.topLevelRuleRecord(
currRuleName,
originalGrammarAction
)
})
this.gastProductionsCache[currRuleName] = recordedRuleGast
})
} finally {
this.disableRecording()
}
})
let resolverErrors = []
this.TRACE_INIT("Grammar Resolving", () => {
resolverErrors = resolveGrammar({
rules: values(this.gastProductionsCache)
})
this.definitionErrors.push.apply(this.definitionErrors, resolverErrors) // mutability for the win?
})
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) {
let validationErrors = validateGrammar({
rules: values(this.gastProductionsCache),
maxLookahead: this.maxLookahead,
tokenTypes: values(this.tokensMap),
errMsgProvider: defaultGrammarValidatorErrorProvider,
grammarName: className
})
this.definitionErrors.push.apply(
this.definitionErrors,
validationErrors
) // mutability for the win?
}
})
// 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", () => {
let allFollows = computeAllProdsFollows(
values(this.gastProductionsCache)
)
this.resyncFollows = allFollows
})
}
this.TRACE_INIT("ComputeLookaheadFunctions", () => {
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://sap.github.io/chevrotain/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES\n\t" +
"For further details."
)
}
this.skipValidations = has(config, "skipValidations")
? config.skipValidations
: 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: IParserConfig = DEFAULT_PARSER_CONFIG
) {
const configClone = cloneObj(config)
configClone.outputCst = true
super(tokenVocabulary, configClone)
}
}
export class EmbeddedActionsParser extends Parser {
constructor(
tokenVocabulary: TokenVocabulary,
config: IParserConfig = DEFAULT_PARSER_CONFIG
) {
const configClone = cloneObj(config)
configClone.outputCst = false
super(tokenVocabulary, configClone)
}
}

View file

@ -0,0 +1,71 @@
### Parser Traits (mixins)
The Chevrotain Parser class is implemented as multiple classes mixed-in (combined) together
to provide the required functionality.
A mix-in approach has been chosen to:
1. Split up the large (~3,000 LOC) Parser Class into smaller files.
- Similar to C# [Partial Classes](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods)
2. Avoid performance regressions of the common "classic" Object oriented pattern of composition & delegation.
- For example, consider: `this.LA()` vs `this.traitX.LA()`
- Past attempts at extracting an API for Lexer Adaptors using composition have [shown](https://github.com/SAP/chevrotain/issues/528#issuecomment-313863665)
significant performance regressions.
### Background
The mixin pattern used here is derived from the mixins pattern found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/mixins.html).
There are a few Issues With The TypeScript Handbook Pattern:
- Duplicate declarations of the instance methods.
- Missing scenario of interaction between different mixins included by the same class.
- This scenario is more similar to partial classes.
- Instance fields initialization would not be executed.
- As instance fields are copied to the constructor by the TypeScript compiler
and only one constructor would get invoked.
Therefor a slightly modified pattern has been used.
### The "Upgraded" Pattern
The building blocks are as follows:
- **Define the full combined type**
- by using Intersection Types to define the complete Type (after all the mixins).
- [Source Snippet](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/parse/parser/traits/parser_traits.ts#L20-L28)
- **Make every method aware of its full execution context**
- By specifying the type of "this" context in methods as the "full combined type" Type
to allow "interaction" between different mixed-ins of the same class.
- [e.g calling a method from another trait](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/parse/parser/traits/recognizer_api.ts#L35-L41)
- **Define state initialization for each trait/mixin**?:
- By defining init methods for each trait/mixin which would be called during the combined type initialization.
- [A single trait's initializer definition](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/parse/parser/traits/lexer_adapter.ts#L17-L21)
- **Invoke the state initialization in the combined class**
- By using a type assertion in the main class constructor to enable calling these init methods.
- [Invoking the init methods](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/parse/parser/parser.ts#L225-L232)
- **Ensuring alignment with the public API**
- By using a dummy assignment statement to leverge the TypeScript compiler to ensure the internal Parser implementation matches
the exposed public API of Chevrotain.
- [Dummy assignment](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/api.ts#L193-L197)
- **Enriching the combined class's prototype**
- [Invoking ApplyMixings](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/parse/parser/parser.ts#L240-L249)
- [Upgraded ApplyMixings with setter/getter handling](https://github.com/SAP/chevrotain/blob/8a1c3594165849c179f6c9fd67078ba96af0ea34/src/utils/utils.ts#L433-L460)
* Pros
- Avoid duplication.
- Allows splitting up large classes to multiple files if/when class composition is not appropriate.
- a.k.a ["partial classes"](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods).
* Cons
- Breaks the semantics of TypeScript a bit, What the compiler knows about "SmartObject"
is no longer the "full story".
### References
- [Wikipedia Article on Mixins](https://en.wikipedia.org/wiki/Mixin)
- [Trait Linearization in Scala](https://www.trivento.io/trait-linearization/)
### To Investigate
Would TypeScript 2.2 enabled a simpler & clearer pattern by using its support for ["mixin classes"](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#support-for-mix-in-classes)?

View file

@ -0,0 +1,51 @@
import {
ISyntacticContentAssistPath,
IToken,
ITokenGrammarPath,
TokenType
} from "../../../../api"
import {
NextAfterTokenWalker,
nextPossibleTokensAfter
} from "../../grammar/interpreter"
import { first, isUndefined } from "../../../utils/utils"
import { MixedInParser } from "./parser_traits"
export class ContentAssist {
initContentAssist() {}
public computeContentAssist(
this: MixedInParser,
startRuleName: string,
precedingInput: IToken[]
): ISyntacticContentAssistPath[] {
let startRuleGast = this.gastProductionsCache[startRuleName]
if (isUndefined(startRuleGast)) {
throw Error(`Rule ->${startRuleName}<- does not exist in this grammar.`)
}
return nextPossibleTokensAfter(
[startRuleGast],
precedingInput,
this.tokenMatcher,
this.maxLookahead
)
}
// TODO: should this be a member method or a utility? it does not have any state or usage of 'this'...
// TODO: should this be more explicitly part of the public API?
public getNextPossibleTokenTypes(
this: MixedInParser,
grammarPath: ITokenGrammarPath
): TokenType[] {
let topRuleName = first(grammarPath.ruleStack)
let gastProductions = this.getGAstProductions()
let topProduction = gastProductions[topRuleName]
let nextPossibleTokenTypes = new NextAfterTokenWalker(
topProduction,
grammarPath
).startWalking()
return nextPossibleTokenTypes
}
}

View file

@ -0,0 +1,122 @@
import {
IParserConfig,
IParserErrorMessageProvider,
IRecognitionException
} from "../../../../api"
import {
EarlyExitException,
isRecognitionException,
NoViableAltException
} from "../../exceptions_public"
import { cloneArr, has } from "../../../utils/utils"
import {
getLookaheadPathsForOptionalProd,
getLookaheadPathsForOr,
PROD_TYPE
} from "../../grammar/lookahead"
import { MixedInParser } from "./parser_traits"
import { DEFAULT_PARSER_CONFIG } from "../parser"
/**
* Trait responsible for runtime parsing errors.
*/
export class ErrorHandler {
_errors: IRecognitionException[]
errorMessageProvider: IParserErrorMessageProvider
initErrorHandler(config: IParserConfig) {
this._errors = []
this.errorMessageProvider = has(config, "errorMessageProvider")
? config.errorMessageProvider
: DEFAULT_PARSER_CONFIG.errorMessageProvider
}
SAVE_ERROR(
this: MixedInParser,
error: IRecognitionException
): IRecognitionException {
if (isRecognitionException(error)) {
error.context = {
ruleStack: this.getHumanReadableRuleStack(),
ruleOccurrenceStack: cloneArr(this.RULE_OCCURRENCE_STACK)
}
this._errors.push(error)
return error
} else {
throw Error("Trying to save an Error which is not a RecognitionException")
}
}
get errors(): IRecognitionException[] {
return cloneArr(this._errors)
}
set errors(newErrors: IRecognitionException[]) {
this._errors = newErrors
}
// TODO: consider caching the error message computed information
raiseEarlyExitException(
this: MixedInParser,
occurrence: number,
prodType: PROD_TYPE,
userDefinedErrMsg: string
): void {
let ruleName = this.getCurrRuleFullName()
let ruleGrammar = this.getGAstProductions()[ruleName]
let lookAheadPathsPerAlternative = getLookaheadPathsForOptionalProd(
occurrence,
ruleGrammar,
prodType,
this.maxLookahead
)
let insideProdPaths = lookAheadPathsPerAlternative[0]
let actualTokens = []
for (let i = 1; i <= this.maxLookahead; i++) {
actualTokens.push(this.LA(i))
}
let msg = this.errorMessageProvider.buildEarlyExitMessage({
expectedIterationPaths: insideProdPaths,
actual: actualTokens,
previous: this.LA(0),
customUserDescription: userDefinedErrMsg,
ruleName: ruleName
})
throw this.SAVE_ERROR(new EarlyExitException(msg, this.LA(1), this.LA(0)))
}
// TODO: consider caching the error message computed information
raiseNoAltException(
this: MixedInParser,
occurrence: number,
errMsgTypes: string
): void {
let ruleName = this.getCurrRuleFullName()
let ruleGrammar = this.getGAstProductions()[ruleName]
// TODO: getLookaheadPathsForOr can be slow for large enough maxLookahead and certain grammars, consider caching ?
let lookAheadPathsPerAlternative = getLookaheadPathsForOr(
occurrence,
ruleGrammar,
this.maxLookahead
)
let actualTokens = []
for (let i = 1; i <= this.maxLookahead; i++) {
actualTokens.push(this.LA(i))
}
let previousToken = this.LA(0)
let errMsg = this.errorMessageProvider.buildNoViableAltMessage({
expectedPathsPerAlt: lookAheadPathsPerAlternative,
actual: actualTokens,
previous: previousToken,
customUserDescription: errMsgTypes,
ruleName: this.getCurrRuleFullName()
})
throw this.SAVE_ERROR(
new NoViableAltException(errMsg, this.LA(1), previousToken)
)
}
}

View file

@ -0,0 +1,443 @@
import {
AtLeastOneSepMethodOpts,
ConsumeMethodOpts,
CstNode,
DSLMethodOpts,
DSLMethodOptsWithErr,
GrammarAction,
IOrAlt,
IParserConfig,
IProduction,
IToken,
ManySepMethodOpts,
OrMethodOpts,
SubruleMethodOpts,
TokenType
} from "../../../../api"
import {
forEach,
has,
isArray,
isFunction,
peek,
some
} from "../../../utils/utils"
import { MixedInParser } from "./parser_traits"
import {
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal
} from "../../grammar/gast/gast_public"
import { Lexer } from "../../../scan/lexer_public"
import { augmentTokenTypes, hasShortKeyProperty } from "../../../scan/tokens"
import { createToken, createTokenInstance } from "../../../scan/tokens_public"
import { END_OF_FILE } from "../parser"
import { BITS_FOR_OCCURRENCE_IDX } from "../../grammar/keys"
type ProdWithDef = IProduction & { definition?: IProduction[] }
const RECORDING_NULL_OBJECT = {
description: "This Object indicates the Parser is during Recording Phase"
}
Object.freeze(RECORDING_NULL_OBJECT)
const HANDLE_SEPARATOR = true
const MAX_METHOD_IDX = Math.pow(2, BITS_FOR_OCCURRENCE_IDX) - 1
const RFT = createToken({ name: "RECORDING_PHASE_TOKEN", pattern: Lexer.NA })
augmentTokenTypes([RFT])
const RECORDING_PHASE_TOKEN = createTokenInstance(
RFT,
"This IToken indicates the Parser is in Recording Phase\n\t" +
"" +
"See: https://sap.github.io/chevrotain/docs/guide/internals.html#grammar-recording for details",
// Using "-1" instead of NaN (as in EOF) because an actual number is less likely to
// cause errors if the output of LA or CONSUME would be (incorrectly) used during the recording phase.
-1,
-1,
-1,
-1,
-1,
-1
)
Object.freeze(RECORDING_PHASE_TOKEN)
const RECORDING_PHASE_CSTNODE: CstNode = {
name:
"This CSTNode indicates the Parser is in Recording Phase\n\t" +
"See: https://sap.github.io/chevrotain/docs/guide/internals.html#grammar-recording for details",
children: {}
}
/**
* This trait handles the creation of the GAST structure for Chevrotain Grammars
*/
export class GastRecorder {
recordingProdStack: ProdWithDef[]
RECORDING_PHASE: boolean
initGastRecorder(this: MixedInParser, config: IParserConfig): void {
this.recordingProdStack = []
this.RECORDING_PHASE = false
}
enableRecording(this: MixedInParser): void {
this.RECORDING_PHASE = true
this.TRACE_INIT("Enable Recording", () => {
/**
* Warning Dark Voodoo Magic upcoming!
* We are "replacing" the public parsing DSL methods API
* With **new** alternative implementations on the Parser **instance**
*
* So far this is the only way I've found to avoid performance regressions during parsing time.
* - Approx 30% performance regression was measured on Chrome 75 Canary when attempting to replace the "internal"
* implementations directly instead.
*/
for (let i = 0; i < 10; i++) {
const idx = i > 0 ? i : ""
this[`CONSUME${idx}`] = function (arg1, arg2) {
return this.consumeInternalRecord(arg1, i, arg2)
}
this[`SUBRULE${idx}`] = function (arg1, arg2) {
return this.subruleInternalRecord(arg1, i, arg2)
}
this[`OPTION${idx}`] = function (arg1) {
return this.optionInternalRecord(arg1, i)
}
this[`OR${idx}`] = function (arg1) {
return this.orInternalRecord(arg1, i)
}
this[`MANY${idx}`] = function (arg1) {
this.manyInternalRecord(i, arg1)
}
this[`MANY_SEP${idx}`] = function (arg1) {
this.manySepFirstInternalRecord(i, arg1)
}
this[`AT_LEAST_ONE${idx}`] = function (arg1) {
this.atLeastOneInternalRecord(i, arg1)
}
this[`AT_LEAST_ONE_SEP${idx}`] = function (arg1) {
this.atLeastOneSepFirstInternalRecord(i, arg1)
}
}
// DSL methods with the idx(suffix) as an argument
this[`consume`] = function (idx, arg1, arg2) {
return this.consumeInternalRecord(arg1, idx, arg2)
}
this[`subrule`] = <any>function (idx, arg1, arg2) {
return this.subruleInternalRecord(arg1, idx, arg2)
}
this[`option`] = function (idx, arg1) {
return this.optionInternalRecord(arg1, idx)
}
this[`or`] = function (idx, arg1) {
return this.orInternalRecord(arg1, idx)
}
this[`many`] = function (idx, arg1) {
this.manyInternalRecord(idx, arg1)
}
this[`atLeastOne`] = function (idx, arg1) {
this.atLeastOneInternalRecord(idx, arg1)
}
this.ACTION = this.ACTION_RECORD
this.BACKTRACK = this.BACKTRACK_RECORD
this.LA = this.LA_RECORD
})
}
disableRecording(this: MixedInParser) {
this.RECORDING_PHASE = false
// By deleting these **instance** properties, any future invocation
// will be deferred to the original methods on the **prototype** object
// This seems to get rid of any incorrect optimizations that V8 may
// do during the recording phase.
this.TRACE_INIT("Deleting Recording methods", () => {
for (let i = 0; i < 10; i++) {
const idx = i > 0 ? i : ""
delete this[`CONSUME${idx}`]
delete this[`SUBRULE${idx}`]
delete this[`OPTION${idx}`]
delete this[`OR${idx}`]
delete this[`MANY${idx}`]
delete this[`MANY_SEP${idx}`]
delete this[`AT_LEAST_ONE${idx}`]
delete this[`AT_LEAST_ONE_SEP${idx}`]
}
delete this[`consume`]
delete this[`subrule`]
delete this[`option`]
delete this[`or`]
delete this[`many`]
delete this[`atLeastOne`]
delete this.ACTION
delete this.BACKTRACK
delete this.LA
})
}
// TODO: is there any way to use this method to check no
// Parser methods are called inside an ACTION?
// Maybe try/catch/finally on ACTIONS while disabling the recorders state changes?
ACTION_RECORD<T>(this: MixedInParser, impl: () => T): T {
// NO-OP during recording
return
}
// Executing backtracking logic will break our recording logic assumptions
BACKTRACK_RECORD<T>(
grammarRule: (...args: any[]) => T,
args?: any[]
): () => boolean {
return () => true
}
// LA is part of the official API and may be used for custom lookahead logic
// by end users who may forget to wrap it in ACTION or inside a GATE
LA_RECORD(howMuch: number): IToken {
// We cannot use the RECORD_PHASE_TOKEN here because someone may depend
// On LA return EOF at the end of the input so an infinite loop may occur.
return END_OF_FILE
}
topLevelRuleRecord(name: string, def: Function): Rule {
try {
const newTopLevelRule = new Rule({ definition: [], name: name })
newTopLevelRule.name = name
this.recordingProdStack.push(newTopLevelRule)
def.call(this)
this.recordingProdStack.pop()
return newTopLevelRule
} catch (originalError) {
if (originalError.KNOWN_RECORDER_ERROR !== true) {
try {
originalError.message =
originalError.message +
'\n\t This error was thrown during the "grammar recording phase" For more info see:\n\t' +
"https://sap.github.io/chevrotain/docs/guide/internals.html#grammar-recording"
} catch (mutabilityError) {
// We may not be able to modify the original error object
throw originalError
}
}
throw originalError
}
}
// Implementation of parsing DSL
optionInternalRecord<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>,
occurrence: number
): OUT {
return recordProd.call(this, Option, actionORMethodDef, occurrence)
}
atLeastOneInternalRecord<OUT>(
this: MixedInParser,
occurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOptsWithErr<OUT>
): void {
recordProd.call(this, RepetitionMandatory, actionORMethodDef, occurrence)
}
atLeastOneSepFirstInternalRecord<OUT>(
this: MixedInParser,
occurrence: number,
options: AtLeastOneSepMethodOpts<OUT>
): void {
recordProd.call(
this,
RepetitionMandatoryWithSeparator,
options,
occurrence,
HANDLE_SEPARATOR
)
}
manyInternalRecord<OUT>(
this: MixedInParser,
occurrence: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): void {
recordProd.call(this, Repetition, actionORMethodDef, occurrence)
}
manySepFirstInternalRecord<OUT>(
this: MixedInParser,
occurrence: number,
options: ManySepMethodOpts<OUT>
): void {
recordProd.call(
this,
RepetitionWithSeparator,
options,
occurrence,
HANDLE_SEPARATOR
)
}
orInternalRecord<T>(
this: MixedInParser,
altsOrOpts: IOrAlt<any>[] | OrMethodOpts<unknown>,
occurrence: number
): T {
return recordOrProd.call(this, altsOrOpts, occurrence)
}
subruleInternalRecord<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
occurrence: number,
options?: SubruleMethodOpts
): T | CstNode {
assertMethodIdxIsValid(occurrence)
if (!ruleToCall || has(ruleToCall, "ruleName") === false) {
const error: any = new Error(
`<SUBRULE${getIdxSuffix(occurrence)}> argument is invalid` +
` expecting a Parser method reference but got: <${JSON.stringify(
ruleToCall
)}>` +
`\n inside top level rule: <${
(<Rule>this.recordingProdStack[0]).name
}>`
)
error.KNOWN_RECORDER_ERROR = true
throw error
}
const prevProd: any = peek(this.recordingProdStack)
const ruleName = ruleToCall["ruleName"]
const newNoneTerminal = new NonTerminal({
idx: occurrence,
nonTerminalName: ruleName,
// The resolving of the `referencedRule` property will be done once all the Rule's GASTs have been created
referencedRule: undefined
})
prevProd.definition.push(newNoneTerminal)
return this.outputCst ? RECORDING_PHASE_CSTNODE : <any>RECORDING_NULL_OBJECT
}
consumeInternalRecord(
this: MixedInParser,
tokType: TokenType,
occurrence: number,
options: ConsumeMethodOpts
): IToken {
assertMethodIdxIsValid(occurrence)
if (!hasShortKeyProperty(tokType)) {
const error: any = new Error(
`<CONSUME${getIdxSuffix(occurrence)}> argument is invalid` +
` expecting a TokenType reference but got: <${JSON.stringify(
tokType
)}>` +
`\n inside top level rule: <${
(<Rule>this.recordingProdStack[0]).name
}>`
)
error.KNOWN_RECORDER_ERROR = true
throw error
}
const prevProd: any = peek(this.recordingProdStack)
const newNoneTerminal = new Terminal({
idx: occurrence,
terminalType: tokType
})
prevProd.definition.push(newNoneTerminal)
return RECORDING_PHASE_TOKEN
}
}
function recordProd(
prodConstructor: any,
mainProdArg: any,
occurrence: number,
handleSep: boolean = false
): any {
assertMethodIdxIsValid(occurrence)
const prevProd: any = peek(this.recordingProdStack)
const grammarAction = isFunction(mainProdArg) ? mainProdArg : mainProdArg.DEF
const newProd = new prodConstructor({ definition: [], idx: occurrence })
if (handleSep) {
newProd.separator = mainProdArg.SEP
}
if (has(mainProdArg, "MAX_LOOKAHEAD")) {
newProd.maxLookahead = mainProdArg.MAX_LOOKAHEAD
}
this.recordingProdStack.push(newProd)
grammarAction.call(this)
prevProd.definition.push(newProd)
this.recordingProdStack.pop()
return RECORDING_NULL_OBJECT
}
function recordOrProd(mainProdArg: any, occurrence: number): any {
assertMethodIdxIsValid(occurrence)
const prevProd: any = peek(this.recordingProdStack)
// Only an array of alternatives
const hasOptions = isArray(mainProdArg) === false
const alts = hasOptions === false ? mainProdArg : mainProdArg.DEF
const newOrProd = new Alternation({
definition: [],
idx: occurrence,
ignoreAmbiguities: hasOptions && mainProdArg.IGNORE_AMBIGUITIES === true
})
if (has(mainProdArg, "MAX_LOOKAHEAD")) {
newOrProd.maxLookahead = mainProdArg.MAX_LOOKAHEAD
}
const hasPredicates = some(alts, (currAlt: any) => isFunction(currAlt.GATE))
newOrProd.hasPredicates = hasPredicates
prevProd.definition.push(newOrProd)
forEach(alts, (currAlt) => {
const currAltFlat = new Alternative({ definition: [] })
newOrProd.definition.push(currAltFlat)
if (has(currAlt, "IGNORE_AMBIGUITIES")) {
currAltFlat.ignoreAmbiguities = currAlt.IGNORE_AMBIGUITIES
}
// **implicit** ignoreAmbiguities due to usage of gate
else if (has(currAlt, "GATE")) {
currAltFlat.ignoreAmbiguities = true
}
this.recordingProdStack.push(currAltFlat)
currAlt.ALT.call(this)
this.recordingProdStack.pop()
})
return RECORDING_NULL_OBJECT
}
function getIdxSuffix(idx: number): string {
return idx === 0 ? "" : `${idx}`
}
function assertMethodIdxIsValid(idx): void {
if (idx < 0 || idx > MAX_METHOD_IDX) {
const error: any = new Error(
// The stack trace will contain all the needed details
`Invalid DSL Method idx value: <${idx}>\n\t` +
`Idx value must be a none negative value smaller than ${
MAX_METHOD_IDX + 1
}`
)
error.KNOWN_RECORDER_ERROR = true
throw error
}
}

View file

@ -0,0 +1,86 @@
import { END_OF_FILE } from "../parser"
import { IToken } from "../../../../api"
import { MixedInParser } from "./parser_traits"
/**
* Trait responsible abstracting over the interaction with Lexer output (Token vector).
*
* This could be generalized to support other kinds of lexers, e.g.
* - Just in Time Lexing / Lexer-Less parsing.
* - Streaming Lexer.
*/
export class LexerAdapter {
tokVector: IToken[]
tokVectorLength
currIdx: number
initLexerAdapter() {
this.tokVector = []
this.tokVectorLength = 0
this.currIdx = -1
}
set input(newInput: IToken[]) {
// @ts-ignore - `this parameter` not supported in setters/getters
// - https://www.typescriptlang.org/docs/handbook/functions.html#this-parameters
if (this.selfAnalysisDone !== true) {
throw Error(
`Missing <performSelfAnalysis> invocation at the end of the Parser's constructor.`
)
}
// @ts-ignore - `this parameter` not supported in setters/getters
// - https://www.typescriptlang.org/docs/handbook/functions.html#this-parameters
this.reset()
this.tokVector = newInput
this.tokVectorLength = newInput.length
}
get input(): IToken[] {
return this.tokVector
}
// skips a token and returns the next token
SKIP_TOKEN(this: MixedInParser): IToken {
if (this.currIdx <= this.tokVector.length - 2) {
this.consumeToken()
return this.LA(1)
} else {
return END_OF_FILE
}
}
// Lexer (accessing Token vector) related methods which can be overridden to implement lazy lexers
// or lexers dependent on parser context.
LA(this: MixedInParser, howMuch: number): IToken {
const soughtIdx = this.currIdx + howMuch
if (soughtIdx < 0 || this.tokVectorLength <= soughtIdx) {
return END_OF_FILE
} else {
return this.tokVector[soughtIdx]
}
}
consumeToken(this: MixedInParser) {
this.currIdx++
}
exportLexerState(this: MixedInParser): number {
return this.currIdx
}
importLexerState(this: MixedInParser, newState: number) {
this.currIdx = newState
}
resetLexerState(this: MixedInParser): void {
this.currIdx = -1
}
moveToTerminatedState(this: MixedInParser): void {
this.currIdx = this.tokVector.length - 1
}
getLexerPosition(this: MixedInParser): number {
return this.exportLexerState()
}
}

View file

@ -0,0 +1,252 @@
import {
buildAlternativesLookAheadFunc,
buildLookaheadFuncForOptionalProd,
buildLookaheadFuncForOr,
buildSingleAlternativeLookaheadFunction,
PROD_TYPE
} from "../../grammar/lookahead"
import { forEach, has, isES2015MapSupported } from "../../../utils/utils"
import {
DEFAULT_PARSER_CONFIG,
lookAheadSequence,
TokenMatcher
} from "../parser"
import { IOrAlt, IParserConfig } from "../../../../api"
import {
AT_LEAST_ONE_IDX,
AT_LEAST_ONE_SEP_IDX,
getKeyForAutomaticLookahead,
MANY_IDX,
MANY_SEP_IDX,
OPTION_IDX,
OR_IDX
} from "../../grammar/keys"
import { MixedInParser } from "./parser_traits"
import { Rule } from "../../grammar/gast/gast_public"
import { collectMethods, getProductionDslName } from "../../grammar/gast/gast"
/**
* Trait responsible for the lookahead related utilities and optimizations.
*/
export class LooksAhead {
maxLookahead: number
lookAheadFuncsCache: any
dynamicTokensEnabled: boolean
initLooksAhead(config: IParserConfig) {
this.dynamicTokensEnabled = has(config, "dynamicTokensEnabled")
? config.dynamicTokensEnabled
: DEFAULT_PARSER_CONFIG.dynamicTokensEnabled
this.maxLookahead = has(config, "maxLookahead")
? config.maxLookahead
: DEFAULT_PARSER_CONFIG.maxLookahead
/* istanbul ignore next - Using plain array as dictionary will be tested on older node.js versions and IE11 */
this.lookAheadFuncsCache = isES2015MapSupported() ? new Map() : []
// Performance optimization on newer engines that support ES6 Map
// For larger Maps this is slightly faster than using a plain object (array in our case).
/* istanbul ignore else - The else branch will be tested on older node.js versions and IE11 */
if (isES2015MapSupported()) {
this.getLaFuncFromCache = this.getLaFuncFromMap
this.setLaFuncCache = this.setLaFuncCacheUsingMap
} else {
this.getLaFuncFromCache = this.getLaFuncFromObj
this.setLaFuncCache = this.setLaFuncUsingObj
}
}
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 = buildLookaheadFuncForOr(
currProd.idx,
currRule,
currProd.maxLookahead || this.maxLookahead,
currProd.hasPredicates,
this.dynamicTokensEnabled,
this.lookAheadBuilderForAlternatives
)
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,
PROD_TYPE.REPETITION,
currProd.maxLookahead,
getProductionDslName(currProd)
)
})
forEach(option, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
OPTION_IDX,
PROD_TYPE.OPTION,
currProd.maxLookahead,
getProductionDslName(currProd)
)
})
forEach(repetitionMandatory, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
AT_LEAST_ONE_IDX,
PROD_TYPE.REPETITION_MANDATORY,
currProd.maxLookahead,
getProductionDslName(currProd)
)
})
forEach(repetitionMandatoryWithSeparator, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
AT_LEAST_ONE_SEP_IDX,
PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,
currProd.maxLookahead,
getProductionDslName(currProd)
)
})
forEach(repetitionWithSeparator, (currProd) => {
this.computeLookaheadFunc(
currRule,
currProd.idx,
MANY_SEP_IDX,
PROD_TYPE.REPETITION_WITH_SEPARATOR,
currProd.maxLookahead,
getProductionDslName(currProd)
)
})
})
})
}
computeLookaheadFunc(
this: MixedInParser,
rule: Rule,
prodOccurrence: number,
prodKey: number,
prodType: PROD_TYPE,
prodMaxLookahead: number,
dslMethodName: string
): void {
this.TRACE_INIT(
`${dslMethodName}${prodOccurrence === 0 ? "" : prodOccurrence}`,
() => {
const laFunc = buildLookaheadFuncForOptionalProd(
prodOccurrence,
rule,
prodMaxLookahead || this.maxLookahead,
this.dynamicTokensEnabled,
prodType,
this.lookAheadBuilderForOptional
)
const key = getKeyForAutomaticLookahead(
this.fullRuleNameToShort[rule.name],
prodKey,
prodOccurrence
)
this.setLaFuncCache(key, laFunc)
}
)
}
lookAheadBuilderForOptional(
this: MixedInParser,
alt: lookAheadSequence,
tokenMatcher: TokenMatcher,
dynamicTokensEnabled: boolean
): () => boolean {
return buildSingleAlternativeLookaheadFunction(
alt,
tokenMatcher,
dynamicTokensEnabled
)
}
lookAheadBuilderForAlternatives(
this: MixedInParser,
alts: lookAheadSequence[],
hasPredicates: boolean,
tokenMatcher: TokenMatcher,
dynamicTokensEnabled: boolean
): (orAlts?: IOrAlt<any>[]) => number | undefined {
return buildAlternativesLookAheadFunc(
alts,
hasPredicates,
tokenMatcher,
dynamicTokensEnabled
)
}
// this actually returns a number, but it is always used as a string (object prop key)
getKeyForAutomaticLookahead(
this: MixedInParser,
dslMethodIdx: number,
occurrence: number
): number {
let currRuleShortName: any = this.getLastExplicitRuleShortName()
return getKeyForAutomaticLookahead(
currRuleShortName,
dslMethodIdx,
occurrence
)
}
/* istanbul ignore next */
getLaFuncFromCache(this: MixedInParser, key: number): Function {
return undefined
}
getLaFuncFromMap(this: MixedInParser, key: number): Function {
return this.lookAheadFuncsCache.get(key)
}
/* istanbul ignore next - Using plain array as dictionary will be tested on older node.js versions and IE11 */
getLaFuncFromObj(this: MixedInParser, key: number): Function {
return this.lookAheadFuncsCache[key]
}
/* istanbul ignore next */
setLaFuncCache(this: MixedInParser, key: number, value: Function): void {}
setLaFuncCacheUsingMap(
this: MixedInParser,
key: number,
value: Function
): void {
this.lookAheadFuncsCache.set(key, value)
}
/* istanbul ignore next - Using plain array as dictionary will be tested on older node.js versions and IE11 */
setLaFuncUsingObj(this: MixedInParser, key: number, value: Function): void {
this.lookAheadFuncsCache[key] = value
}
}

View file

@ -0,0 +1,58 @@
import { ErrorHandler } from "./error_handler"
import { LexerAdapter } from "./lexer_adapter"
import { LooksAhead } from "./looksahead"
import { RecognizerApi } from "./recognizer_api"
import { RecognizerEngine } from "./recognizer_engine"
import { Recoverable } from "./recoverable"
import { TreeBuilder } from "./tree_builder"
import {
Parser as ParserConstructorImpel,
CstParser as CstParserConstructorImpel,
EmbeddedActionsParser as EmbeddedActionsParserConstructorImpel
} from "../parser"
import * as defs from "../../../../api"
import { ContentAssist } from "./context_assist"
import { GastRecorder } from "./gast_recorder"
import { PerformanceTracer } from "./perf_tracer"
/**
* This Type combines all the Parser traits.
* It is used in all traits in the "this type assertion"
* - https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#specifying-the-type-of-this-for-functions
* This enables strong Type Checks inside trait methods that invoke methods from other traits.
* This pattern is very similar to "self types" in Scala.
* - https://docs.scala-lang.org/tour/self-types.html
*/
export type MixedInParser = ParserConstructorImpel &
ErrorHandler &
LexerAdapter &
LooksAhead &
RecognizerApi &
RecognizerEngine &
Recoverable &
TreeBuilder &
ContentAssist &
GastRecorder &
PerformanceTracer
interface MixedInCstParserConstructor {
new (
tokenVocabulary: defs.TokenVocabulary,
config?: defs.IParserConfig
): defs.CstParser
}
export const CstParser: MixedInCstParserConstructor = <any>(
CstParserConstructorImpel
)
interface MixedInEmbeddedActionsParserConstructor {
new (
tokenVocabulary: defs.TokenVocabulary,
config?: defs.IParserConfig
): defs.EmbeddedActionsParser
}
export const EmbeddedActionsParser: MixedInEmbeddedActionsParserConstructor = <
any
>EmbeddedActionsParserConstructorImpel

View file

@ -0,0 +1,53 @@
import { IParserConfig } from "../../../../api"
import { has, timer } from "../../../utils/utils"
import { MixedInParser } from "./parser_traits"
import { DEFAULT_PARSER_CONFIG } from "../parser"
/**
* 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
} 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,717 @@
import {
AtLeastOneSepMethodOpts,
ConsumeMethodOpts,
DSLMethodOpts,
DSLMethodOptsWithErr,
GrammarAction,
IOrAlt,
IRuleConfig,
ISerializedGast,
IToken,
ManySepMethodOpts,
OrMethodOpts,
SubruleMethodOpts,
TokenType
} from "../../../../api"
import { contains, values } from "../../../utils/utils"
import { isRecognitionException } from "../../exceptions_public"
import { DEFAULT_RULE_CONFIG, ParserDefinitionErrorType } from "../parser"
import { defaultGrammarValidatorErrorProvider } from "../../errors_public"
import { validateRuleIsOverridden } from "../../grammar/checks"
import { MixedInParser } from "./parser_traits"
import { Rule, serializeGrammar } from "../../grammar/gast/gast_public"
/**
* 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<T>(
this: MixedInParser,
idx: number,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, idx, options)
}
option<OUT>(
this: MixedInParser,
idx: number,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
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<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 0, options)
}
SUBRULE1<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 1, options)
}
SUBRULE2<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 2, options)
}
SUBRULE3<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 3, options)
}
SUBRULE4<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 4, options)
}
SUBRULE5<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 5, options)
}
SUBRULE6<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 6, options)
}
SUBRULE7<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 7, options)
}
SUBRULE8<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 8, options)
}
SUBRULE9<T>(
this: MixedInParser,
ruleToCall: (idx: number) => T,
options?: SubruleMethodOpts
): T {
return this.subruleInternal(ruleToCall, 9, options)
}
OPTION<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 0)
}
OPTION1<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 1)
}
OPTION2<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 2)
}
OPTION3<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 3)
}
OPTION4<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 4)
}
OPTION5<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 5)
}
OPTION6<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 6)
}
OPTION7<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 7)
}
OPTION8<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
return this.optionInternal(actionORMethodDef, 8)
}
OPTION9<OUT>(
this: MixedInParser,
actionORMethodDef: GrammarAction<OUT> | DSLMethodOpts<OUT>
): OUT {
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 (contains(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)
let ruleImplementation = this.defineRule(name, implementation, config)
this[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 {
let ruleErrors = []
ruleErrors = ruleErrors.concat(
validateRuleIsOverridden(name, this.definedRulesNames, this.className)
)
this.definitionErrors.push.apply(this.definitionErrors, ruleErrors) // mutability for the win
let ruleImplementation = this.defineRule(name, impl, config)
this[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))
}
}

View file

@ -0,0 +1,854 @@
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://sap.github.io/chevrotain/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://sap.github.io/chevrotain/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 = []
}
}

View file

@ -0,0 +1,459 @@
import { createTokenInstance, EOF } from "../../../scan/tokens_public"
import {
AbstractNextTerminalAfterProductionWalker,
IFirstAfterRepetition
} from "../../grammar/interpreter"
import {
cloneArr,
contains,
dropRight,
find,
flatten,
has,
isEmpty,
map
} from "../../../utils/utils"
import {
IParserConfig,
IToken,
ITokenGrammarPath,
TokenType
} from "../../../../api"
import { MismatchedTokenException } from "../../exceptions_public"
import { IN } from "../../constants"
import { MixedInParser } from "./parser_traits"
import { DEFAULT_PARSER_CONFIG } from "../parser"
export const EOF_FOLLOW_KEY: any = {}
export interface IFollowKey {
ruleName: string
idxInCallingRule: number
inRule: string
}
export const IN_RULE_RECOVERY_EXCEPTION = "InRuleRecoveryException"
export function InRuleRecoveryException(message: string) {
this.name = IN_RULE_RECOVERY_EXCEPTION
this.message = message
}
InRuleRecoveryException.prototype = Error.prototype
/**
* This trait is responsible for the error recovery and fault tolerant logic
*/
export class Recoverable {
recoveryEnabled: boolean
firstAfterRepMap: Record<string, IFirstAfterRepetition>
resyncFollows: Record<string, TokenType[]>
initRecoverable(config: IParserConfig) {
this.firstAfterRepMap = {}
this.resyncFollows = {}
this.recoveryEnabled = has(config, "recoveryEnabled")
? config.recoveryEnabled
: DEFAULT_PARSER_CONFIG.recoveryEnabled
// performance optimization, NOOP will be inlined which
// effectively means that this optional feature does not exist
// when not used.
if (this.recoveryEnabled) {
this.attemptInRepetitionRecovery = attemptInRepetitionRecovery
}
}
public getTokenToInsert(tokType: TokenType): IToken {
let tokToInsert = createTokenInstance(
tokType,
"",
NaN,
NaN,
NaN,
NaN,
NaN,
NaN
)
tokToInsert.isInsertedInRecovery = true
return tokToInsert
}
public canTokenTypeBeInsertedInRecovery(tokType: TokenType) {
return true
}
tryInRepetitionRecovery(
this: MixedInParser,
grammarRule: Function,
grammarRuleArgs: any[],
lookAheadFunc: () => boolean,
expectedTokType: TokenType
): void {
// TODO: can the resyncTokenType be cached?
let reSyncTokType = this.findReSyncTokenType()
let savedLexerState = this.exportLexerState()
let resyncedTokens = []
let passedResyncPoint = false
let nextTokenWithoutResync = this.LA(1)
let currToken = this.LA(1)
let generateErrorMessage = () => {
let previousToken = this.LA(0)
// we are preemptively re-syncing before an error has been detected, therefor we must reproduce
// the error that would have been thrown
let msg = this.errorMessageProvider.buildMismatchTokenMessage({
expected: expectedTokType,
actual: nextTokenWithoutResync,
previous: previousToken,
ruleName: this.getCurrRuleFullName()
})
let error = new MismatchedTokenException(
msg,
nextTokenWithoutResync,
this.LA(0)
)
// the first token here will be the original cause of the error, this is not part of the resyncedTokens property.
error.resyncedTokens = dropRight(resyncedTokens)
this.SAVE_ERROR(error)
}
while (!passedResyncPoint) {
// re-synced to a point where we can safely exit the repetition/
if (this.tokenMatcher(currToken, expectedTokType)) {
generateErrorMessage()
return // must return here to avoid reverting the inputIdx
} else if (lookAheadFunc.call(this)) {
// we skipped enough tokens so we can resync right back into another iteration of the repetition grammar rule
generateErrorMessage()
// recursive invocation in other to support multiple re-syncs in the same top level repetition grammar rule
grammarRule.apply(this, grammarRuleArgs)
return // must return here to avoid reverting the inputIdx
} else if (this.tokenMatcher(currToken, reSyncTokType)) {
passedResyncPoint = true
} else {
currToken = this.SKIP_TOKEN()
this.addToResyncTokens(currToken, resyncedTokens)
}
}
// we were unable to find a CLOSER point to resync inside the Repetition, reset the state.
// The parsing exception we were trying to prevent will happen in the NEXT parsing step. it may be handled by
// "between rules" resync recovery later in the flow.
this.importLexerState(savedLexerState)
}
shouldInRepetitionRecoveryBeTried(
this: MixedInParser,
expectTokAfterLastMatch: TokenType,
nextTokIdx: number,
notStuck: boolean | undefined
): boolean {
// Edge case of arriving from a MANY repetition which is stuck
// Attempting recovery in this case could cause an infinite loop
if (notStuck === false) {
return false
}
// arguments to try and perform resync into the next iteration of the many are missing
if (expectTokAfterLastMatch === undefined || nextTokIdx === undefined) {
return false
}
// no need to recover, next token is what we expect...
if (this.tokenMatcher(this.LA(1), expectTokAfterLastMatch)) {
return false
}
// error recovery is disabled during backtracking as it can make the parser ignore a valid grammar path
// and prefer some backtracking path that includes recovered errors.
if (this.isBackTracking()) {
return false
}
// if we can perform inRule recovery (single token insertion or deletion) we always prefer that recovery algorithm
// because if it works, it makes the least amount of changes to the input stream (greedy algorithm)
//noinspection RedundantIfStatementJS
if (
this.canPerformInRuleRecovery(
expectTokAfterLastMatch,
this.getFollowsForInRuleRecovery(expectTokAfterLastMatch, nextTokIdx)
)
) {
return false
}
return true
}
// Error Recovery functionality
getFollowsForInRuleRecovery(
this: MixedInParser,
tokType: TokenType,
tokIdxInRule: number
): TokenType[] {
let grammarPath = this.getCurrentGrammarPath(tokType, tokIdxInRule)
let follows = this.getNextPossibleTokenTypes(grammarPath)
return follows
}
tryInRuleRecovery(
this: MixedInParser,
expectedTokType: TokenType,
follows: TokenType[]
): IToken {
if (this.canRecoverWithSingleTokenInsertion(expectedTokType, follows)) {
let tokToInsert = this.getTokenToInsert(expectedTokType)
return tokToInsert
}
if (this.canRecoverWithSingleTokenDeletion(expectedTokType)) {
let nextTok = this.SKIP_TOKEN()
this.consumeToken()
return nextTok
}
throw new InRuleRecoveryException("sad sad panda")
}
canPerformInRuleRecovery(
this: MixedInParser,
expectedToken: TokenType,
follows: TokenType[]
): boolean {
return (
this.canRecoverWithSingleTokenInsertion(expectedToken, follows) ||
this.canRecoverWithSingleTokenDeletion(expectedToken)
)
}
canRecoverWithSingleTokenInsertion(
this: MixedInParser,
expectedTokType: TokenType,
follows: TokenType[]
): boolean {
if (!this.canTokenTypeBeInsertedInRecovery(expectedTokType)) {
return false
}
// must know the possible following tokens to perform single token insertion
if (isEmpty(follows)) {
return false
}
let mismatchedTok = this.LA(1)
let isMisMatchedTokInFollows =
find(follows, (possibleFollowsTokType: TokenType) => {
return this.tokenMatcher(mismatchedTok, possibleFollowsTokType)
}) !== undefined
return isMisMatchedTokInFollows
}
canRecoverWithSingleTokenDeletion(
this: MixedInParser,
expectedTokType: TokenType
): boolean {
let isNextTokenWhatIsExpected = this.tokenMatcher(
this.LA(2),
expectedTokType
)
return isNextTokenWhatIsExpected
}
isInCurrentRuleReSyncSet(
this: MixedInParser,
tokenTypeIdx: TokenType
): boolean {
let followKey = this.getCurrFollowKey()
let currentRuleReSyncSet = this.getFollowSetFromFollowKey(followKey)
return contains(currentRuleReSyncSet, tokenTypeIdx)
}
findReSyncTokenType(this: MixedInParser): TokenType {
let allPossibleReSyncTokTypes = this.flattenFollowSet()
// this loop will always terminate as EOF is always in the follow stack and also always (virtually) in the input
let nextToken = this.LA(1)
let k = 2
while (true) {
let nextTokenType: any = nextToken.tokenType
if (contains(allPossibleReSyncTokTypes, nextTokenType)) {
return nextTokenType
}
nextToken = this.LA(k)
k++
}
}
getCurrFollowKey(this: MixedInParser): IFollowKey {
// the length is at least one as we always add the ruleName to the stack before invoking the rule.
if (this.RULE_STACK.length === 1) {
return EOF_FOLLOW_KEY
}
let currRuleShortName = this.getLastExplicitRuleShortName()
let currRuleIdx = this.getLastExplicitRuleOccurrenceIndex()
let prevRuleShortName = this.getPreviousExplicitRuleShortName()
return {
ruleName: this.shortRuleNameToFullName(currRuleShortName),
idxInCallingRule: currRuleIdx,
inRule: this.shortRuleNameToFullName(prevRuleShortName)
}
}
buildFullFollowKeyStack(this: MixedInParser): IFollowKey[] {
let explicitRuleStack = this.RULE_STACK
let explicitOccurrenceStack = this.RULE_OCCURRENCE_STACK
return map(explicitRuleStack, (ruleName, idx) => {
if (idx === 0) {
return EOF_FOLLOW_KEY
}
return {
ruleName: this.shortRuleNameToFullName(ruleName),
idxInCallingRule: explicitOccurrenceStack[idx],
inRule: this.shortRuleNameToFullName(explicitRuleStack[idx - 1])
}
})
}
flattenFollowSet(this: MixedInParser): TokenType[] {
let followStack = map(this.buildFullFollowKeyStack(), (currKey) => {
return this.getFollowSetFromFollowKey(currKey)
})
return <any>flatten(followStack)
}
getFollowSetFromFollowKey(
this: MixedInParser,
followKey: IFollowKey
): TokenType[] {
if (followKey === EOF_FOLLOW_KEY) {
return [EOF]
}
let followName =
followKey.ruleName + followKey.idxInCallingRule + IN + followKey.inRule
return this.resyncFollows[followName]
}
// It does not make any sense to include a virtual EOF token in the list of resynced tokens
// as EOF does not really exist and thus does not contain any useful information (line/column numbers)
addToResyncTokens(
this: MixedInParser,
token: IToken,
resyncTokens: IToken[]
): IToken[] {
if (!this.tokenMatcher(token, EOF)) {
resyncTokens.push(token)
}
return resyncTokens
}
reSyncTo(this: MixedInParser, tokType: TokenType): IToken[] {
let resyncedTokens = []
let nextTok = this.LA(1)
while (this.tokenMatcher(nextTok, tokType) === false) {
nextTok = this.SKIP_TOKEN()
this.addToResyncTokens(nextTok, resyncedTokens)
}
// the last token is not part of the error.
return dropRight(resyncedTokens)
}
attemptInRepetitionRecovery(
this: MixedInParser,
prodFunc: Function,
args: any[],
lookaheadFunc: () => boolean,
dslMethodIdx: number,
prodOccurrence: number,
nextToksWalker: typeof AbstractNextTerminalAfterProductionWalker,
notStuck?: boolean
): void {
// by default this is a NO-OP
// The actual implementation is with the function(not method) below
}
getCurrentGrammarPath(
this: MixedInParser,
tokType: TokenType,
tokIdxInRule: number
): ITokenGrammarPath {
let pathRuleStack: string[] = this.getHumanReadableRuleStack()
let pathOccurrenceStack: number[] = cloneArr(this.RULE_OCCURRENCE_STACK)
let grammarPath: any = {
ruleStack: pathRuleStack,
occurrenceStack: pathOccurrenceStack,
lastTok: tokType,
lastTokOccurrence: tokIdxInRule
}
return grammarPath
}
getHumanReadableRuleStack(this: MixedInParser): string[] {
return map(this.RULE_STACK, (currShortName) =>
this.shortRuleNameToFullName(currShortName)
)
}
}
export function attemptInRepetitionRecovery(
this: MixedInParser,
prodFunc: Function,
args: any[],
lookaheadFunc: () => boolean,
dslMethodIdx: number,
prodOccurrence: number,
nextToksWalker: typeof AbstractNextTerminalAfterProductionWalker,
notStuck?: boolean
) {
let key = this.getKeyForAutomaticLookahead(dslMethodIdx, prodOccurrence)
let firstAfterRepInfo = this.firstAfterRepMap[key]
if (firstAfterRepInfo === undefined) {
let currRuleName = this.getCurrRuleFullName()
let ruleGrammar = this.getGAstProductions()[currRuleName]
let walker: AbstractNextTerminalAfterProductionWalker = new nextToksWalker(
ruleGrammar,
prodOccurrence
)
firstAfterRepInfo = walker.startWalking()
this.firstAfterRepMap[key] = firstAfterRepInfo
}
let expectTokAfterLastMatch = firstAfterRepInfo.token
let nextTokIdx = firstAfterRepInfo.occurrence
let isEndOfRule = firstAfterRepInfo.isEndOfRule
// special edge case of a TOP most repetition after which the input should END.
// this will force an attempt for inRule recovery in that scenario.
if (
this.RULE_STACK.length === 1 &&
isEndOfRule &&
expectTokAfterLastMatch === undefined
) {
expectTokAfterLastMatch = EOF
nextTokIdx = 1
}
if (
this.shouldInRepetitionRecoveryBeTried(
expectTokAfterLastMatch,
nextTokIdx,
notStuck
)
) {
// TODO: performance optimization: instead of passing the original args here, we modify
// the args param (or create a new one) and make sure the lookahead func is explicitly provided
// to avoid searching the cache for it once more.
this.tryInRepetitionRecovery(
prodFunc,
args,
lookaheadFunc,
expectTokAfterLastMatch
)
}
}

View file

@ -0,0 +1,275 @@
import {
addNoneTerminalToCst,
addTerminalToCst,
setNodeLocationFull,
setNodeLocationOnlyOffset
} from "../../cst/cst"
import { has, isUndefined, keys, NOOP } from "../../../utils/utils"
import {
createBaseSemanticVisitorConstructor,
createBaseVisitorConstructorWithDefaults
} from "../../cst/cst_visitor"
import {
CstNode,
CstNodeLocation,
ICstVisitor,
IParserConfig,
IToken,
nodeLocationTrackingOptions
} from "../../../../api"
import { MixedInParser } from "./parser_traits"
import { DEFAULT_PARSER_CONFIG } from "../parser"
/**
* This trait is responsible for the CST building logic.
*/
export class TreeBuilder {
outputCst: boolean
CST_STACK: CstNode[]
baseCstVisitorConstructor: Function
baseCstVisitorWithDefaultsConstructor: Function
// dynamically assigned Methods
setNodeLocationFromNode: (
nodeLocation: CstNodeLocation,
locationInformation: CstNodeLocation
) => void
setNodeLocationFromToken: (
nodeLocation: CstNodeLocation,
locationInformation: CstNodeLocation
) => void
cstPostRule: (this: MixedInParser, ruleCstNode: CstNode) => void
setInitialNodeLocation: (cstNode: CstNode) => void
nodeLocationTracking: nodeLocationTrackingOptions
initTreeBuilder(this: MixedInParser, config: IParserConfig) {
this.CST_STACK = []
// outputCst is no longer exposed/defined in the pubic API
this.outputCst = (config as any).outputCst
this.nodeLocationTracking = has(config, "nodeLocationTracking")
? config.nodeLocationTracking
: DEFAULT_PARSER_CONFIG.nodeLocationTracking
if (!this.outputCst) {
this.cstInvocationStateUpdate = NOOP
this.cstFinallyStateUpdate = NOOP
this.cstPostTerminal = NOOP
this.cstPostNonTerminal = NOOP
this.cstPostRule = NOOP
} else {
if (/full/i.test(this.nodeLocationTracking)) {
if (this.recoveryEnabled) {
this.setNodeLocationFromToken = setNodeLocationFull
this.setNodeLocationFromNode = setNodeLocationFull
this.cstPostRule = NOOP
this.setInitialNodeLocation = this.setInitialNodeLocationFullRecovery
} else {
this.setNodeLocationFromToken = NOOP
this.setNodeLocationFromNode = NOOP
this.cstPostRule = this.cstPostRuleFull
this.setInitialNodeLocation = this.setInitialNodeLocationFullRegular
}
} else if (/onlyOffset/i.test(this.nodeLocationTracking)) {
if (this.recoveryEnabled) {
this.setNodeLocationFromToken = <any>setNodeLocationOnlyOffset
this.setNodeLocationFromNode = <any>setNodeLocationOnlyOffset
this.cstPostRule = NOOP
this.setInitialNodeLocation = this.setInitialNodeLocationOnlyOffsetRecovery
} else {
this.setNodeLocationFromToken = NOOP
this.setNodeLocationFromNode = NOOP
this.cstPostRule = this.cstPostRuleOnlyOffset
this.setInitialNodeLocation = this.setInitialNodeLocationOnlyOffsetRegular
}
} else if (/none/i.test(this.nodeLocationTracking)) {
this.setNodeLocationFromToken = NOOP
this.setNodeLocationFromNode = NOOP
this.cstPostRule = NOOP
this.setInitialNodeLocation = NOOP
} else {
throw Error(
`Invalid <nodeLocationTracking> config option: "${config.nodeLocationTracking}"`
)
}
}
}
setInitialNodeLocationOnlyOffsetRecovery(
this: MixedInParser,
cstNode: any
): void {
cstNode.location = {
startOffset: NaN,
endOffset: NaN
}
}
setInitialNodeLocationOnlyOffsetRegular(
this: MixedInParser,
cstNode: any
): void {
cstNode.location = {
// without error recovery the starting Location of a new CstNode is guaranteed
// To be the next Token's startOffset (for valid inputs).
// For invalid inputs there won't be any CSTOutput so this potential
// inaccuracy does not matter
startOffset: this.LA(1).startOffset,
endOffset: NaN
}
}
setInitialNodeLocationFullRecovery(this: MixedInParser, cstNode: any): void {
cstNode.location = {
startOffset: NaN,
startLine: NaN,
startColumn: NaN,
endOffset: NaN,
endLine: NaN,
endColumn: NaN
}
}
/**
* @see setInitialNodeLocationOnlyOffsetRegular for explanation why this work
* @param cstNode
*/
setInitialNodeLocationFullRegular(this: MixedInParser, cstNode: any): void {
const nextToken = this.LA(1)
cstNode.location = {
startOffset: nextToken.startOffset,
startLine: nextToken.startLine,
startColumn: nextToken.startColumn,
endOffset: NaN,
endLine: NaN,
endColumn: NaN
}
}
cstInvocationStateUpdate(
this: MixedInParser,
fullRuleName: string,
shortName: string | number
): void {
const cstNode: CstNode = {
name: fullRuleName,
children: {}
}
this.setInitialNodeLocation(cstNode)
this.CST_STACK.push(cstNode)
}
cstFinallyStateUpdate(this: MixedInParser): void {
this.CST_STACK.pop()
}
cstPostRuleFull(this: MixedInParser, ruleCstNode: CstNode): void {
const prevToken = this.LA(0)
const loc = ruleCstNode.location
// If this condition is true it means we consumed at least one Token
// In this CstNode.
if (loc.startOffset <= prevToken.startOffset === true) {
loc.endOffset = prevToken.endOffset
loc.endLine = prevToken.endLine
loc.endColumn = prevToken.endColumn
}
// "empty" CstNode edge case
else {
loc.startOffset = NaN
loc.startLine = NaN
loc.startColumn = NaN
}
}
cstPostRuleOnlyOffset(this: MixedInParser, ruleCstNode: CstNode): void {
const prevToken = this.LA(0)
const loc = ruleCstNode.location
// If this condition is true it means we consumed at least one Token
// In this CstNode.
if (loc.startOffset <= prevToken.startOffset === true) {
loc.endOffset = prevToken.endOffset
}
// "empty" CstNode edge case
else {
loc.startOffset = NaN
}
}
cstPostTerminal(
this: MixedInParser,
key: string,
consumedToken: IToken
): void {
const rootCst = this.CST_STACK[this.CST_STACK.length - 1]
addTerminalToCst(rootCst, consumedToken, key)
// This is only used when **both** error recovery and CST Output are enabled.
this.setNodeLocationFromToken(rootCst.location, <any>consumedToken)
}
cstPostNonTerminal(
this: MixedInParser,
ruleCstResult: CstNode,
ruleName: string
): void {
const preCstNode = this.CST_STACK[this.CST_STACK.length - 1]
addNoneTerminalToCst(preCstNode, ruleName, ruleCstResult)
// This is only used when **both** error recovery and CST Output are enabled.
this.setNodeLocationFromNode(preCstNode.location, ruleCstResult.location)
}
getBaseCstVisitorConstructor<IN = any, OUT = any>(
this: MixedInParser
): {
new (...args: any[]): ICstVisitor<IN, OUT>
} {
if (isUndefined(this.baseCstVisitorConstructor)) {
const newBaseCstVisitorConstructor = createBaseSemanticVisitorConstructor(
this.className,
keys(this.gastProductionsCache)
)
this.baseCstVisitorConstructor = newBaseCstVisitorConstructor
return newBaseCstVisitorConstructor
}
return <any>this.baseCstVisitorConstructor
}
getBaseCstVisitorConstructorWithDefaults<IN = any, OUT = any>(
this: MixedInParser
): {
new (...args: any[]): ICstVisitor<IN, OUT>
} {
if (isUndefined(this.baseCstVisitorWithDefaultsConstructor)) {
const newConstructor = createBaseVisitorConstructorWithDefaults(
this.className,
keys(this.gastProductionsCache),
this.getBaseCstVisitorConstructor()
)
this.baseCstVisitorWithDefaultsConstructor = newConstructor
return newConstructor
}
return <any>this.baseCstVisitorWithDefaultsConstructor
}
getLastExplicitRuleShortName(this: MixedInParser): string {
let ruleStack = this.RULE_STACK
return ruleStack[ruleStack.length - 1]
}
getPreviousExplicitRuleShortName(this: MixedInParser): string {
let ruleStack = this.RULE_STACK
return ruleStack[ruleStack.length - 2]
}
getLastExplicitRuleOccurrenceIndex(this: MixedInParser): number {
let occurrenceStack = this.RULE_OCCURRENCE_STACK
return occurrenceStack[occurrenceStack.length - 1]
}
}