From 466d2f018cc0dd6943a6dfbb5b82b4546482ce8f Mon Sep 17 00:00:00 2001 From: Mike Lischke Date: Thu, 8 Feb 2024 19:54:11 +0100 Subject: [PATCH] one more Signed-off-by: Mike Lischke --- src/atn/ATNConfigSet.ts | 131 ++++++++++++++++++++-------------- src/atn/LexerATNSimulator.ts | 6 +- src/atn/ParserATNSimulator.ts | 26 +++---- src/atn/PredictionMode.ts | 18 ++--- 4 files changed, 104 insertions(+), 77 deletions(-) diff --git a/src/atn/ATNConfigSet.ts b/src/atn/ATNConfigSet.ts index a4ba981..9103159 100644 --- a/src/atn/ATNConfigSet.ts +++ b/src/atn/ATNConfigSet.ts @@ -38,9 +38,25 @@ const equalATNConfigs = (a: ATNConfig, b: ATNConfig): boolean => { * graph-structured stack */ export class ATNConfigSet { - // Track the elements as they are added to the set; supports get(i)/// + /** + * The reason that we need this is because we don't want the hash map to use + * the standard hash code and equals. We need all configurations with the + * same + * {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively + * doubles + * the number of objects associated with ATNConfigs. The other solution is + * to + * use a hash table that lets us specify the equals/hashCode operation. + * All configs but hashed by (s, i, _, pi) not including context. Wiped out + * when we go readonly as this set becomes a DFA state + */ + public configLookup: HashSet | null = new HashSet(hashATNConfig, equalATNConfigs); + + // Track the elements as they are added to the set; supports get(i). public configs: ATNConfig[] = []; + public uniqueAlt = 0; + /** * Used in parser and lexer. In lexer, it indicates we hit a pred * while computing a closure operation. Don't make a DFA state from this @@ -53,25 +69,7 @@ export class ATNConfigSet { * LL prediction. It will be used to determine how to merge $. With SLL * it's a wildcard whereas it is not for LL context merge */ - public readonly fullCtx: boolean; - - public uniqueAlt = 0; - - /** - * The reason that we need this is because we don't want the hash map to use - * the standard hash code and equals. We need all configurations with the - * same - * {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively - * doubles - * the number of objects associated with ATNConfigs. The other solution is - * to - * use a hash table that lets us specify the equals/hashCode operation. - * All configs but hashed by (s, i, _, pi) not including context. Wiped out - * when we go readonly as this set becomes a DFA state - */ - public configLookup = new HashSet(hashATNConfig, equalATNConfigs); - - public conflictingAlts: BitSet | null = null; + public readonly fullCtx: boolean = false; /** * Indicates that the set of configurations is read-only. Do not @@ -82,11 +80,26 @@ export class ATNConfigSet { */ public readOnly = false; + public conflictingAlts: BitSet | null = null; + private cachedHashCode = -1; - // TODO: add iterator for configs. - public constructor(fullCtx?: boolean) { - this.fullCtx = fullCtx ?? true; + public constructor(fullCtxOrOldSet?: boolean | ATNConfigSet) { + if (fullCtxOrOldSet instanceof ATNConfigSet) { + const old = fullCtxOrOldSet; + + this.addAll(old.configs); + this.uniqueAlt = old.uniqueAlt; + this.conflictingAlts = old.conflictingAlts; + this.hasSemanticContext = old.hasSemanticContext; + this.dipsIntoOuterContext = old.dipsIntoOuterContext; + } else { + this.fullCtx = fullCtxOrOldSet ?? true; + } + } + + public [Symbol.iterator](): IterableIterator { + return this.configs[Symbol.iterator](); } /** @@ -100,52 +113,69 @@ export class ATNConfigSet { * {@link hasSemanticContext} when necessary.

*/ public add(config: ATNConfig, - mergeCache?: DoubleDict | null): boolean { - if (mergeCache === undefined) { - mergeCache = null; - } - + mergeCache: DoubleDict | null = null): boolean { if (this.readOnly) { throw new Error("This set is readonly"); } + if (config.semanticContext !== SemanticContext.NONE) { this.hasSemanticContext = true; } + if (config.reachesIntoOuterContext > 0) { this.dipsIntoOuterContext = true; } - const existing = this.configLookup.add(config); + + const existing = this.configLookup!.add(config); if (existing === config) { this.cachedHashCode = -1; this.configs.push(config); // track order here return true; } + // a previous (s,i,pi,_), merge with it and save result const rootIsWildcard = !this.fullCtx; const merged = merge(existing.context!, config.context!, rootIsWildcard, mergeCache); + /** * no need to check for existing.context, config.context in cache * since only way to create new graphs is "call rule" and here. We * cache at both places */ existing.reachesIntoOuterContext = Math.max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext); + // make sure to preserve the precedence filter suppression during the merge if (config.precedenceFilterSuppressed) { existing.precedenceFilterSuppressed = true; } + existing.context = merged; // replace context; no need to alt mapping return true; } - public getStates(): HashSet { - const states = new HashSet(); + /** Return a List holding list of configs */ + public get elements(): ATNConfig[] { + return this.configs; + } + + /** + * Gets the complete set of represented alternatives for the configuration set. + * + * @returns the set of represented alternatives in this configuration set + */ + public getAlts(): BitSet { + const alts = new BitSet(); for (const config of this.configs) { - states.add(config.state); + alts.set(config.alt); } - return states; + return alts; + } + + public get(i: number): ATNConfig { + return this.configs[i]; } public getPredicates(): SemanticContext[] { @@ -159,12 +189,21 @@ export class ATNConfigSet { return preds; } + public getStates(): HashSet { + const states = new HashSet(); + for (const config of this.configs) { + states.add(config.state); + } + + return states; + } + public optimizeConfigs(interpreter: ATNSimulator): void { if (this.readOnly) { throw new Error("This set is readonly"); } - if (this.configLookup.length === 0) { + if (this.configLookup!.length === 0) { return; } @@ -175,7 +214,7 @@ export class ATNConfigSet { public addAll(coll: ATNConfig[]): boolean { for (const config of coll) { - this.add(config, null); + this.add(config); } return false; @@ -210,6 +249,10 @@ export class ATNConfigSet { } } + public get length(): number { + return this.configs.length; + } + public isEmpty(): boolean { return this.configs.length === 0; } @@ -242,7 +285,7 @@ export class ATNConfigSet { public setReadonly(readOnly: boolean): void { this.readOnly = readOnly; if (readOnly) { - this.configLookup = new HashSet(); // can't mod, no need for lookup cache + this.configLookup = null; // can't mod, no need for lookup cache } } @@ -254,20 +297,4 @@ export class ATNConfigSet { (this.dipsIntoOuterContext ? ",dipsIntoOuterContext" : ""); } - public get items(): ATNConfig[] { - return this.configs; - } - - public get length(): number { - return this.configs.length; - } - - public getAlts(): BitSet { - const alts = new BitSet(); - for (const config of this.configs) { - alts.set(config.alt); - } - - return alts; - } } diff --git a/src/atn/LexerATNSimulator.ts b/src/atn/LexerATNSimulator.ts index da8c400..423b52f 100644 --- a/src/atn/LexerATNSimulator.ts +++ b/src/atn/LexerATNSimulator.ts @@ -289,7 +289,7 @@ export class LexerATNSimulator extends ATNSimulator { // Fill reach starting from closure, following t transitions this.getReachableConfigSet(input, s.configs, reach, t); - if (reach.items.length === 0) { // we got nowhere on t from s + if (reach.length === 0) { // we got nowhere on t from s if (!reach.hasSemanticContext) { // we got nowhere on t, don't throw out this knowledge; it'd // cause a failover from DFA later. @@ -330,7 +330,7 @@ export class LexerATNSimulator extends ATNSimulator { // this is used to skip processing for configs which have a lower priority // than a config that already reached an accept state for the same rule let skipAlt = ATN.INVALID_ALT_NUMBER; - for (const cfg of closure.items) { + for (const cfg of closure) { const currentAltReachedAcceptState = (cfg.alt === skipAlt); if (currentAltReachedAcceptState && (cfg as LexerATNConfig).passedThroughNonGreedyDecision) { continue; @@ -650,7 +650,7 @@ export class LexerATNSimulator extends ATNSimulator { protected addDFAState(configs: ATNConfigSet): DFAState { const proposed = new DFAState(configs); let firstConfigWithRuleStopState = null; - for (const cfg of configs.items) { + for (const cfg of configs) { if (cfg.state instanceof RuleStopState) { firstConfigWithRuleStopState = cfg; break; diff --git a/src/atn/ParserATNSimulator.ts b/src/atn/ParserATNSimulator.ts index b67e29b..ac0182e 100644 --- a/src/atn/ParserATNSimulator.ts +++ b/src/atn/ParserATNSimulator.ts @@ -321,7 +321,7 @@ export class ParserATNSimulator extends ATNSimulator { protected static getUniqueAlt(configs: ATNConfigSet): number { let alt = ATN.INVALID_ALT_NUMBER; - for (const c of configs.items) { + for (const c of configs) { if (alt === ATN.INVALID_ALT_NUMBER) { alt = c.alt; // found first alt } else if (c.alt !== alt) { @@ -662,7 +662,7 @@ export class ParserATNSimulator extends ATNSimulator { */ public dumpDeadEndConfigs(e: NoViableAltException): void { console.log("dead end configs: "); - const decs = e.deadEndConfigs!.items; + const decs = e.deadEndConfigs!; for (const c of decs) { let trans = "no edges"; if (c.state.transitions.length > 0) { @@ -828,7 +828,7 @@ export class ParserATNSimulator extends ATNSimulator { let skippedStopStates = null; // First figure out where we can reach on input t - for (const c of closure.items) { + for (const c of closure) { if (ParserATNSimulator.debug) { console.log("testing " + this.getTokenName(t) + " at " + c); } @@ -869,7 +869,7 @@ export class ParserATNSimulator extends ATNSimulator { // withheld in skippedStopStates, or when the current symbol is EOF. // if (skippedStopStates === null && t !== Token.EOF) { - if (intermediate.items.length === 1) { + if (intermediate.length === 1) { // Don't pursue the closure if there is just one state. // It can only have one alternative; just add to result // Also don't pursue the closure if there is unique alternative @@ -888,7 +888,7 @@ export class ParserATNSimulator extends ATNSimulator { reach = new ATNConfigSet(fullCtx); const closureBusy = new HashSet(); const treatEofAsEpsilon = t === Token.EOF; - for (const config of intermediate.items) { + for (const config of intermediate) { this.closure(config, reach, closureBusy, false, fullCtx, treatEofAsEpsilon); } } @@ -931,7 +931,7 @@ export class ParserATNSimulator extends ATNSimulator { console.log("computeReachSet " + closure + " -> " + reach); } - if (reach.items.length === 0) { + if (reach.length === 0) { return null; } else { return reach; @@ -964,7 +964,7 @@ export class ParserATNSimulator extends ATNSimulator { } const result = new ATNConfigSet(configs.fullCtx); - for (const config of configs.items) { + for (const config of configs) { if (config.state instanceof RuleStopState) { result.add(config, this.mergeCache); continue; @@ -1061,7 +1061,7 @@ export class ParserATNSimulator extends ATNSimulator { protected applyPrecedenceFilter(configs: ATNConfigSet): ATNConfigSet { const statesFromAlt1 = []; const configSet = new ATNConfigSet(configs.fullCtx); - for (const config of configs.items) { + for (const config of configs) { // handle alt 1 first if (config.alt !== 1) { continue; @@ -1079,7 +1079,7 @@ export class ParserATNSimulator extends ATNSimulator { } } - for (const config of configs.items) { + for (const config of configs) { if (config.alt === 1) { // already handled continue; @@ -1123,7 +1123,7 @@ export class ParserATNSimulator extends ATNSimulator { // From this, it is clear that NONE||anything==NONE. // let altToPred: Array | null = []; - for (const c of configs.items) { + for (const c of configs) { if (ambigAlts.get(c.alt)) { altToPred[c.alt] = SemanticContext.orContext(altToPred[c.alt] ?? null, c.semanticContext); } @@ -1229,7 +1229,7 @@ export class ParserATNSimulator extends ATNSimulator { } // Is there a syntactically valid path with a failed pred? - if (semInvalidConfigs.items.length > 0) { + if (semInvalidConfigs.length > 0) { alt = this.getAltThatFinishedDecisionEntryRule(semInvalidConfigs); if (alt !== ATN.INVALID_ALT_NUMBER) { // syntactically viable path exists return alt; @@ -1241,7 +1241,7 @@ export class ParserATNSimulator extends ATNSimulator { protected getAltThatFinishedDecisionEntryRule(configs: ATNConfigSet): number { const alts = []; - for (const c of configs.items) { + for (const c of configs) { if (c.reachesIntoOuterContext > 0 || ((c.state instanceof RuleStopState) && c.context!.hasEmptyPath())) { if (alts.indexOf(c.alt) < 0) { alts.push(c.alt); @@ -1270,7 +1270,7 @@ export class ParserATNSimulator extends ATNSimulator { const succeeded = new ATNConfigSet(configs.fullCtx); const failed = new ATNConfigSet(configs.fullCtx); - for (const c of configs.items) { + for (const c of configs) { if (c.semanticContext !== SemanticContext.NONE) { const predicateEvaluationResult = c.semanticContext.evaluate(this.parser, outerContext); if (predicateEvaluationResult) { diff --git a/src/atn/PredictionMode.ts b/src/atn/PredictionMode.ts index 42ad410..23aa521 100644 --- a/src/atn/PredictionMode.ts +++ b/src/atn/PredictionMode.ts @@ -195,7 +195,7 @@ export class PredictionMode { if (configs.hasSemanticContext) { // dup configs, tossing out semantic predicates const dup = new ATNConfigSet(); - for (let c of configs.items) { + for (let c of configs) { c = new ATNConfig({ semanticContext: SemanticContext.NONE }, c); dup.add(c); } @@ -220,7 +220,7 @@ export class PredictionMode { * {@link RuleStopState}, otherwise {@code false} */ public static hasConfigInRuleStopState(configs: ATNConfigSet): boolean { - for (const c of configs.items) { + for (const c of configs) { if (c.state instanceof RuleStopState) { return true; } @@ -240,7 +240,7 @@ export class PredictionMode { * {@link RuleStopState}, otherwise {@code false} */ public static allConfigsInRuleStopStates(configs: ATNConfigSet): boolean { - for (const c of configs.items) { + for (const c of configs) { if (!(c.state instanceof RuleStopState)) { return false; } @@ -513,14 +513,14 @@ export class PredictionMode { }, ); - configs.items.forEach((cfg) => { + for (const cfg of configs) { let alts = configToAlts.get(cfg); if (alts === null) { alts = new BitSet(); configToAlts.set(cfg, alts); } alts.set(cfg.alt); - }); + } return configToAlts.getValues(); }; @@ -535,14 +535,14 @@ export class PredictionMode { */ public static getStateToAltMap(configs: ATNConfigSet): HashMap { const m = new HashMap(); - configs.items.forEach((c) => { + for (const c of configs) { let alts = m.get(c.state); if (!alts) { alts = new BitSet(); m.set(c.state, alts); } alts.set(c.alt); - }); + } return m; }; @@ -550,13 +550,13 @@ export class PredictionMode { public static hasStateAssociatedWithOneAlt(configs: ATNConfigSet): boolean { // Count how many alts per state there are in the configs. const counts: { [key: number]: number; } = {}; - configs.items.forEach((c) => { + for (const c of configs) { const stateNumber = c.state.stateNumber; if (!counts[stateNumber]) { counts[stateNumber] = 0; } counts[stateNumber]++; - }); + } return Object.values(counts).some((count) => { return count === 1; }); };