diff --git a/.eslintrc.json b/.eslintrc.json index bf0fd38..216ebb4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,7 @@ ], "ignorePatterns": [ "src/**/*.js", + "src/tree/xpath/XPathLexer.ts", "spec/**/*.*js", "dist/**/*", "cli/index.js", @@ -536,7 +537,7 @@ ], "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-parameter-properties": "off", - "@typescript-eslint/no-use-before-define": "error", + "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-unsafe-assignment": "off", // TODO: enable "@typescript-eslint/no-unsafe-member-access": "off", // TODO: enable "@typescript-eslint/no-unsafe-call": "error", // TODO: enable @@ -610,6 +611,7 @@ ], "@typescript-eslint/restrict-template-expressions": "off", "@typescript-eslint/restrict-plus-operands": "off", + "@typescript-eslint/no-base-to-string": "off", "jsdoc/check-alignment": "error", "jsdoc/check-indentation": "off", "jsdoc/require-param-type": "off", @@ -620,6 +622,7 @@ { "startLines": 1 } - ] + ], + "jsdoc/no-undefined-types": "off" } } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index e3c9829..6db26c8 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -5,7 +5,7 @@ name: Build & Test on: push: - branches: [ master ] + branches: [ master, ts-migration ] pull_request: branches: [ master ] diff --git a/.vscode/launch.json b/.vscode/launch.json index a723c75..ed729ef 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,9 @@ "type": "node", "request": "launch", "name": "Run current Jest test", - "runtimeExecutable": null, + "runtimeExecutable": "node", "runtimeArgs": [ + "--experimental-vm-modules", "${workspaceRoot}/node_modules/.bin/jest", "${fileBasenameNoExtension}.ts", "--no-coverage", @@ -38,6 +39,7 @@ "ts-node/esm", "tests/benchmarks/run-benchmarks.ts", ], + "sourceMaps": true, } ] } diff --git a/ReadMe.md b/ReadMe.md index 5fc9ad4..c7f2b49 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -6,23 +6,18 @@ # TypeScript Runtime for ANTLR 4 -This package is a fork of the official ANTLR4 JavaScript runtime (with its TypeScript additions), with the following changes: +This package is a fork of the official ANTLR4 JavaScript runtime and has been fully transformed to TypeScript. Other improvements are: -- Much improved TypeScript type definitions. - XPath implementation. - Vocabulary implementation. - Complete Interval implementation. - Parser and lexer interpreters. -- A couple of bug fixes. -- Consistent formatting (indentation, semicolons, spaces, etc.). -- Project folder structure is now similar to the Java runtime. -- Numerous smaller fixes (`null` instead of `undefined` and others). +- Numerous bug fixes and other changes. - Smaller node package (no test specs or other unnecessary files). - No CommonJS support anymore (ESM only). No differentiation between node and browser environments. -- Build is now based on esbuild. - Includes the `antlr4ng-cli` tool to generate parser files compatible with this runtime. This tool uses a custom build of the ANTLR4 tool. -It is (mostly) a drop-in replacement of the `antlr4` package, and can be used as such. For more information about ANTLR see www.antlr.org. Read more details about the [JavaScript](https://github.com/antlr/antlr4/blob/master/doc/javascript-target.md) and [TypeScript](https://github.com/antlr/antlr4/blob/master/doc/typescript-target.md) targets at the provided links, but keep in mind that this documentation applies to the original JS/TS target. +This package is a blend of the original JS implementation and antlr4ts, which is a TypeScript implementation of the ANTLR4 runtime, but was abandoned. It tries to keep the best of both worlds, while following the Java runtime as close as possible. It's a bit slower than the JS runtime, but faster than antlr4ts. ## Installation @@ -39,9 +34,22 @@ npm install --save-dev antlr4ng-cli ``` See [its readme](./cli/ReadMe.md) for more information. +If you come from one of the other JS/TS runtimes, you may have to adjust your code a bit. The antlr4ng package more strictly exposes the Java nullability for certain members. This will require that you either use the non-null assertion operator to force the compiler to accept your code, or you have to check for nullability before accessing a member. The latter is the recommended way, as it is safer. + +Additionally, some members have been renamed to more TypeScript like names (e.g. Parser._ctx is now Parser.context). The following table shows the most important changes: + +| Old Name | New Name | +| -------- | -------- | +| Parser._ctx | Parser.context | +| Parser._errHandler | Parser.errorHandler | +| Parser._input | Parser.inputStream | +| Parser._interp | Parser.interpreter | + +The package requires ES2022 or newer, for features like static initialization blocks in classes and private fields (`#field`). It is recommended to use the latest TypeScript version. + ## Benchmarks -This runtime is constantly monitored for performance regressions. The following table shows the results of the benchmarks run on last release: +This runtime is monitored for performance regressions. The following table shows the results of the benchmarks run on last release: | Test | Cold Run | Warm Run| | ---- | -------- | ------- | @@ -50,11 +58,11 @@ This runtime is constantly monitored for performance regressions. The following | Large Inserts | 11022 ms | 10616 ms | | Total | 20599 ms | 10978 ms | -The benchmarks consist of a set of query files, which are parsed by a MySQL parser. The query collection file contains more than 900 MySQL queries of all kinds, from very simple to complex stored procedures, including some deeply nested select queries that can easily exhaust available stack space. The minimum MySQL server version used was 8.0.0. +The benchmarks consist of a set of query files, which are parsed by a MySQL parser. The query collection file contains more than 900 MySQL queries of all kinds, from very simple to complex stored procedures, including some deeply nested select queries that can easily exhaust the available stack space (in certain situations, such as parsing in a thread with default stack size). The minimum MySQL server version used was 8.0.0. -The large binary inserts file contains only a few dozen queries, but they are really large with deep recursions, stressing so the prediction engine of the parser. Additionally, one query contains binary (image) data which contains input characters from the whole UTF-8 range. +The large binary inserts file contains only a few dozen queries, but they are really large with deep recursions, so they stress the prediction engine of the parser. In addition, one query contains binary (image) data containing input characters from the entire UTF-8 range. -The example file is a copy of the largest test file in [this repository](https://github.com/antlr/grammars-v4/tree/master/sql/mysql/Positive-Technologies/examples), and is known to be very slow to parse with other parsers, but the one used here. +The example file is a copy of the largest test file in [this repository](https://github.com/antlr/grammars-v4/tree/master/sql/mysql/Positive-Technologies/examples), and is known to be very slow to parse with other MySQL grammars. The one used here, however, is fast. ## Release Notes diff --git a/cli/antlr4-4.13.2-SNAPSHOT-complete.jar b/cli/antlr4-4.13.2-SNAPSHOT-complete.jar index 3d01036..ced93db 100644 Binary files a/cli/antlr4-4.13.2-SNAPSHOT-complete.jar and b/cli/antlr4-4.13.2-SNAPSHOT-complete.jar differ diff --git a/cspell.json b/cspell.json index 5e638d5..06c89db 100644 --- a/cspell.json +++ b/cspell.json @@ -13,6 +13,7 @@ "rdbms", "runtimes", "sakila", + "unpredicated", "whitespaces" ], "ignoreWords": [ @@ -20,16 +21,26 @@ "Dlanguage", "Grosch", "Harwell", + "Hashable", + "IATN", + "Nondisjoint", + "Preds", + "Sethi", + "Ullman", "Wirth", "Xexact", "bitrix", "interp", "localctx", + "longlong", "nbits", + "opnds", "outfile", "parentctx", + "prec", "precpred", "recog", + "semctx", "sempred", "ttype" ], diff --git a/package.json b/package.json index feca28d..c83f1a9 100644 --- a/package.json +++ b/package.json @@ -37,22 +37,20 @@ "typescript": "5.2.2" }, "scripts": { - "prepublishOnly": "npm run build && npm run test", - "build": "npm run generate-test-parser && esbuild ./src/index.js --bundle --outfile=dist/antlr4.mjs --format=esm --sourcemap=external --minify", + "prepublishOnly": "npm run build-minified && npm run test", + "tsc": "tsc --watch", + "build": "npm run generate-test-parser && esbuild ./src/index.js --bundle --outfile=dist/antlr4.mjs --format=esm --sourcemap", + "build-minified": "npm run generate-test-parser && esbuild ./src/index.js --bundle --outfile=dist/antlr4.mjs --format=esm --sourcemap --minify", "full-test": "npm run test && npm run run-benchmarks", "test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --no-coverage", - "lint": "eslint src/", "generate-test-parser": "cli/index.js -Dlanguage=TypeScript -o tests/benchmarks/generated -visitor -listener -Xexact-output-dir tests/benchmarks/MySQLLexer.g4 tests/benchmarks/MySQLParser.g4", - "run-benchmarks": "node --no-warnings --experimental-vm-modules --loader ts-node/esm tests/benchmarks/run-benchmarks.ts" + "generate-xpath-lexer": "cli/index.js -Dlanguage=TypeScript -o src/tree/xpath/generated -no-visitor -no-listener -Xexact-output-dir src/tree/xpath/XPathLexer.g4", + "run-benchmarks": "node --no-warnings --experimental-vm-modules --loader ts-node/esm tests/benchmarks/run-benchmarks.ts", + "profile benchmarks": "node --no-warnings --experimental-vm-modules --prof --loader ts-node/esm tests/benchmarks/run-benchmarks.ts", + "process profile tick file": " node --prof-process isolate-0x130008000-75033-v8.log > processed.txt" }, "exports": { - "types": "./src/index.d.ts", + "types": "./src/index.ts", "default": "./dist/antlr4.mjs" - }, - "babel": { - "presets": [ - "@babel/preset-env" - ], - "targets": "defaults" } } diff --git a/src/ANTLRErrorListener.ts b/src/ANTLRErrorListener.ts new file mode 100644 index 0000000..804c052 --- /dev/null +++ b/src/ANTLRErrorListener.ts @@ -0,0 +1,180 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Parser } from "./Parser.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { Recognizer } from "./Recognizer.js"; +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; +import { DFA } from "./dfa/DFA.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; +import { Token } from "./Token.js"; +import { BitSet } from "./misc/BitSet.js"; + +/** How to emit recognition errors. */ +export interface ANTLRErrorListener { + /** + * Upon syntax error, notify any interested parties. This is not how to + * recover from errors or compute error messages. {@link ANTLRErrorStrategy} + * specifies how to recover from syntax errors and how to compute error + * messages. This listener's job is simply to emit a computed message, + * though it has enough information to create its own message in many cases. + * + *

The {@link RecognitionException} is non-null for all syntax errors except + * when we discover mismatched token errors that we can recover from + * in-line, without returning from the surrounding rule (via the single + * token insertion and deletion mechanism).

+ * + * @param recognizer + * What parser got the error. From this + * object, you can access the context as well + * as the input stream. + * @param offendingSymbol + * The offending token in the input token + * stream, unless recognizer is a lexer (then it's null). If + * no viable alternative error, {@code e} has token at which we + * started production for the decision. + * @param line + * The line number in the input where the error occurred. + * @param charPositionInLine + * The character position within that line where the error occurred. + * @param msg + * The message to emit. + * @param e + * The exception generated by the parser that led to + * the reporting of an error. It is null in the case where + * the parser was able to recover in line without exiting the + * surrounding rule. + */ + syntaxError(recognizer: Recognizer, + offendingSymbol: S | null, + line: number, + charPositionInLine: number, + msg: string, + e: RecognitionException | null): void; + + /** + * This method is called by the parser when a full-context prediction + * results in an ambiguity. + * + *

Each full-context prediction which does not result in a syntax error + * will call either {@link #reportContextSensitivity} or + * {@link #reportAmbiguity}.

+ * + *

When {@code ambigAlts} is not null, it contains the set of potentially + * viable alternatives identified by the prediction algorithm. When + * {@code ambigAlts} is null, use {@link ATNConfigSet#getAlts} to obtain the + * represented alternatives from the {@code configs} argument.

+ * + *

When {@code exact} is {@code true}, all of the potentially + * viable alternatives are truly viable, i.e. this is reporting an exact + * ambiguity. When {@code exact} is {@code false}, at least two of + * the potentially viable alternatives are viable for the current input, but + * the prediction algorithm terminated as soon as it determined that at + * least the minimum potentially viable alternative is truly + * viable.

+ * + *

When the {@link PredictionMode#LL_EXACT_AMBIG_DETECTION} prediction + * mode is used, the parser is required to identify exact ambiguities so + * {@code exact} will always be {@code true}.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input input where the ambiguity was identified + * @param exact {@code true} if the ambiguity is exactly known, otherwise + * {@code false}. This is always {@code true} when + * {@link PredictionMode#LL_EXACT_AMBIG_DETECTION} is used. + * @param ambigAlts the potentially ambiguous alternatives, or {@code null} + * to indicate that the potentially ambiguous alternatives are the complete + * set of represented alternatives in {@code configs} + * @param configs the ATN configuration set where the ambiguity was + * identified + */ + reportAmbiguity(recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + exact: boolean, + ambigAlts: BitSet | null, + configs: ATNConfigSet): void; + + /** + * This method is called when an SLL conflict occurs and the parser is about + * to use the full context information to make an LL decision. + * + *

If one or more configurations in {@code configs} contains a semantic + * predicate, the predicates are evaluated before this method is called. The + * subset of alternatives which are still viable after predicates are + * evaluated is reported in {@code conflictingAlts}.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input index where the SLL conflict occurred + * @param conflictingAlts The specific conflicting alternatives. If this is + * {@code null}, the conflicting alternatives are all alternatives + * represented in {@code configs}. At the moment, conflictingAlts is non-null + * (for the reference implementation, but Sam's optimized version can see this + * as null). + * @param configs the ATN configuration set where the SLL conflict was + * detected + */ + reportAttemptingFullContext(recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + conflictingAlts: BitSet | null, + configs: ATNConfigSet): void; + + /** + * This method is called by the parser when a full-context prediction has a + * unique result. + * + *

Each full-context prediction which does not result in a syntax error + * will call either {@link #reportContextSensitivity} or + * {@link #reportAmbiguity}.

+ * + *

For prediction implementations that only evaluate full-context + * predictions when an SLL conflict is found (including the default + * {@link ParserATNSimulator} implementation), this method reports cases + * where SLL conflicts were resolved to unique full-context predictions, + * i.e. the decision was context-sensitive. This report does not necessarily + * indicate a problem, and it may appear even in completely unambiguous + * grammars.

+ * + *

{@code configs} may have more than one represented alternative if the + * full-context prediction algorithm does not evaluate predicates before + * beginning the full-context prediction. In all cases, the final prediction + * is passed as the {@code prediction} argument.

+ * + *

Note that the definition of "context sensitivity" in this method + * differs from the concept in {@link DecisionInfo#contextSensitivities}. + * This method reports all instances where an SLL conflict occurred but LL + * parsing produced a unique result, whether or not that unique result + * matches the minimum alternative in the SLL conflicting set.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input index where the context sensitivity was + * finally determined + * @param prediction the unambiguous result of the full-context prediction + * @param configs the ATN configuration set where the unambiguous prediction + * was determined + */ + reportContextSensitivity(recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + prediction: number, + configs: ATNConfigSet): void; +} diff --git a/src/ANTLRErrorStrategy.d.ts b/src/ANTLRErrorStrategy.ts similarity index 98% rename from src/ANTLRErrorStrategy.d.ts rename to src/ANTLRErrorStrategy.ts index ff579b4..168f0b7 100644 --- a/src/ANTLRErrorStrategy.d.ts +++ b/src/ANTLRErrorStrategy.ts @@ -25,7 +25,7 @@ import { Token } from "./Token.js"; * *

TODO: what to do about lexers

*/ -export declare interface ANTLRErrorStrategy { +export interface ANTLRErrorStrategy { /** * Reset the error handler state for the specified {@code recognizer}. * diff --git a/src/BailErrorStrategy.d.ts b/src/BailErrorStrategy.d.ts deleted file mode 100644 index 1894ff8..0000000 --- a/src/BailErrorStrategy.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DefaultErrorStrategy } from "./DefaultErrorStrategy.js"; - -export declare class BailErrorStrategy extends DefaultErrorStrategy { - public constructor(); - -} diff --git a/src/BailErrorStrategy.js b/src/BailErrorStrategy.ts similarity index 67% rename from src/BailErrorStrategy.js rename to src/BailErrorStrategy.ts index 99fddba..d9e9004 100644 --- a/src/BailErrorStrategy.js +++ b/src/BailErrorStrategy.ts @@ -4,9 +4,14 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable jsdoc/require-param */ + import { InputMismatchException } from "./InputMismatchException.js"; -import { ParseCancellationException } from "./ParseCancellationException.js"; +import { ParseCancellationException } from "./misc/ParseCancellationException.js"; import { DefaultErrorStrategy } from "./DefaultErrorStrategy.js"; +import { Parser } from "./Parser.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; /** * This implementation of {@link ANTLRErrorStrategy} responds to syntax errors @@ -32,27 +37,23 @@ import { DefaultErrorStrategy } from "./DefaultErrorStrategy.js"; * * *

- * {@code myparser.setErrorHandler(new BailErrorStrategy());}

+ * {@code myParser.setErrorHandler(new BailErrorStrategy());}

* - * @see Parser//setErrorHandler(ANTLRErrorStrategy) - * */ + * @see Parser#setErrorHandler(ANTLRErrorStrategy) + */ export class BailErrorStrategy extends DefaultErrorStrategy { - constructor() { - super(); - } - /** * Instead of recovering from exception {@code e}, re-throw it wrapped * in a {@link ParseCancellationException} so it is not caught by the * rule function catches. Use {@link Exception//getCause()} to get the * original {@link RecognitionException}. */ - recover(recognizer, e) { - let context = recognizer._ctx; + public override recover(recognizer: Parser, e: RecognitionException): void { + let context: ParserRuleContext | null = recognizer.context; while (context !== null) { context.exception = e; - context = context.parent; + context = context.parent as ParserRuleContext; } throw new ParseCancellationException(e); } @@ -61,12 +62,19 @@ export class BailErrorStrategy extends DefaultErrorStrategy { * Make sure we don't attempt to recover inline; if the parser * successfully recovers, it won't throw an exception. */ - recoverInline(recognizer) { - this.recover(recognizer, new InputMismatchException(recognizer)); + public override recoverInline(recognizer: Parser): never { + const exception = new InputMismatchException(recognizer); + let context: ParserRuleContext | null = recognizer.context; + while (context !== null) { + context.exception = exception; + context = context.parent as ParserRuleContext; + } + + throw new ParseCancellationException(exception); } // Make sure we don't attempt to recover from problems in subrules.// - sync(recognizer) { + public override sync(_recognizer: Parser): void { // pass } } diff --git a/src/BaseErrorListener.d.ts b/src/BaseErrorListener.d.ts deleted file mode 100644 index accbc6e..0000000 --- a/src/BaseErrorListener.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Recognizer } from "./Recognizer.js"; -import { Token } from "./Token.js"; -import { ATNConfigSet } from "./atn/ATNConfigSet.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { DFA } from "./dfa/DFA.js"; -import { RecognitionException } from "./RecognitionException.js"; - -export declare class BaseErrorListener { - public syntaxError(recognizer: Recognizer, offendingSymbol: T, line: number, - column: number, msg: string, e: RecognitionException | null): void; - public reportAmbiguity(recognizer: Recognizer, dfa: DFA, startIndex: number, stopIndex: number, - exact: boolean, ambigAlts: unknown, configs: ATNConfigSet): void; - public reportAttemptingFullContext(recognizer: Recognizer, dfa: DFA, startIndex: number, - stopIndex: number, conflictingAlts: unknown, configs: ATNConfigSet): void; - public reportContextSensitivity(recognizer: Recognizer, dfa: DFA, startIndex: number, - stopIndex: number, prediction: number, configs: ATNConfigSet): void; -} diff --git a/src/BaseErrorListener.js b/src/BaseErrorListener.js deleted file mode 100644 index 47bf98d..0000000 --- a/src/BaseErrorListener.js +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * Provides an empty default implementation of {@link BaseErrorListener}. The - * default implementation of each method does nothing, but can be overridden as - * necessary. - */ -export class BaseErrorListener { - syntaxError(recognizer, offendingSymbol, line, column, msg, e) { - } - - reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) { - } - - reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) { - } - - reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) { - } -} diff --git a/src/BaseErrorListener.ts b/src/BaseErrorListener.ts new file mode 100644 index 0000000..f536011 --- /dev/null +++ b/src/BaseErrorListener.ts @@ -0,0 +1,40 @@ +/* Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { ANTLRErrorListener } from "./ANTLRErrorListener.js"; + +import { Parser } from "./Parser.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { Recognizer } from "./Recognizer.js"; +import { Token } from "./Token.js"; +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; +import { DFA } from "./dfa/DFA.js"; +import { BitSet } from "./misc/BitSet.js"; + +/** + * Provides an empty default implementation of {@link ANTLRErrorListener}. The + * default implementation of each method does nothing, but can be overridden as + * necessary. + */ +export class BaseErrorListener implements ANTLRErrorListener { + public syntaxError(recognizer: Recognizer, offendingSymbol: S | null, + line: number, column: number, msg: string, e: RecognitionException | null): void { + } + + public reportAmbiguity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, exact: boolean, + ambigAlts: BitSet | null, configs: ATNConfigSet): void { + } + + public reportAttemptingFullContext(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, + conflictingAlts: BitSet | null, configs: ATNConfigSet): void { + } + + public reportContextSensitivity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, + prediction: number, configs: ATNConfigSet): void { + } +} diff --git a/src/BufferedTokenStream.d.ts b/src/BufferedTokenStream.d.ts deleted file mode 100644 index abfd669..0000000 --- a/src/BufferedTokenStream.d.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { TokenStream } from "./TokenStream.js"; -import { TokenSource } from "./TokenSource.js"; -import { Token } from "./Token.js"; -import type { CommonTokenStream } from "./CommonTokenStream.js"; -import { Interval } from "./misc/Interval.js"; - -/** - * This implementation of {@link TokenStream} loads tokens from a - * {@link TokenSource} on-demand, and places the tokens in a buffer to provide - * access to any previous token by index. - * - *

- * This token stream ignores the value of {@link Token#getChannel}. If your - * parser requires the token stream filter tokens to only those on a particular - * channel, such as {@link Token#DEFAULT_CHANNEL} or - * {@link Token#HIDDEN_CHANNEL}, use a filtering token stream such a - * {@link CommonTokenStream}.

- */ -export declare class BufferedTokenStream implements TokenStream { - /** - * The {@link TokenSource} from which tokens for this stream are fetched. - */ - protected tokenSource: TokenSource; - - /** - * A collection of all tokens fetched from the token source. The list is - * considered a complete view of the input once {@link #fetchedEOF} is set - * to {@code true}. - */ - protected tokens: Token[]; - - /** - * Indicates whether the {@link Token#EOF} token has been fetched from - * {@link #tokenSource} and added to {@link #tokens}. This field improves - * performance for the following cases: - * - *
    - *
  • {@link #consume}: The lookahead check in {@link #consume} to prevent - * consuming the EOF symbol is optimized by checking the values of - * {@link #fetchedEOF} and {@link #p} instead of calling {@link #LA}.
  • - *
  • {@link #fetch}: The check to prevent adding multiple EOF symbols into - * {@link #tokens} is trivial with this field.
  • - *
      - */ - protected fetchedEOF: boolean; - - public constructor(tokenSource: TokenSource); - - public getTokenSource(): TokenSource; - - public mark(): number; - public release(marker: number): void; - - public reset(): void; - public seek(index: number): void; - - public get size(): number; - public get index(): number; - - public consume(): void; - public sync(i: number): number; - public fetch(n: number): void; - - public get(i: number): Token; - public get(start: number, stop: number): Token[]; - - // eslint-disable-next-line @typescript-eslint/naming-convention - public LA(i: number): number; - // eslint-disable-next-line @typescript-eslint/naming-convention - public LT(k: number): Token; - - /** Reset this token stream by setting its token source. */ - public setTokenSource(tokenSource: TokenSource): void; - - /** - * Given a start and stop index, return a List of all tokens in - * the token type BitSet. Return null if no tokens were found. This - * method looks at both on and off channel tokens. - */ - public getTokens(): Token[]; - public getTokens(start: number, stop: number): Token[]; - public getTokens(start: number, stop: number, types: Set): Token[]; - - /** - * Collect all tokens on specified channel to the right of - * the current token up until we see a token on DEFAULT_TOKEN_CHANNEL or - * EOF. If channel is -1, find any non default channel token. - */ - public hiddenTokensToRight(tokenIndex: number): Token[]; - public hiddenTokensToRight(tokenIndex: number, channel: number): Token[]; - - /** - * Collect all tokens on specified channel to the left of - * the current token up until we see a token on DEFAULT_TOKEN_CHANNEL. - * If channel is -1, find any non default channel token. - */ - public hiddenTokensToLeft(tokenIndex: number): Token[]; - public hiddenTokensToLeft(tokenIndex: number, channel: number): Token[]; - - public getSourceName(): string; - - /** Get all tokens from lexer until EOF */ - public fill(): void; - - public getText(): string; - public getText(interval: Interval): string; - - // eslint-disable-next-line @typescript-eslint/naming-convention - protected LB(k: number): Token; - - /** - * Given a starting index, return the index of the next token on channel. - * Return {@code i} if {@code tokens[i]} is on channel. Return the index of - * the EOF token if there are no tokens on channel between {@code i} and - * EOF. - */ - protected nextTokenOnChannel(i: number, channel: number): number; - - /** - * Given a starting index, return the index of the previous token on - * channel. Return {@code i} if {@code tokens[i]} is on channel. Return -1 - * if there are no tokens on channel between {@code i} and 0. - * - *

      - * If {@code i} specifies an index at or after the EOF token, the EOF token - * index is returned. This is due to the fact that the EOF token is treated - * as though it were on every channel.

      - */ - protected previousTokenOnChannel(i: number, channel: number): number; - - protected filterForChannel(left: number, right: number, channel: number): number; - -} diff --git a/src/BufferedTokenStream.js b/src/BufferedTokenStream.ts similarity index 52% rename from src/BufferedTokenStream.js rename to src/BufferedTokenStream.ts index 17891f2..a821f66 100644 --- a/src/BufferedTokenStream.js +++ b/src/BufferedTokenStream.ts @@ -4,10 +4,15 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from './Token.js'; -import { Lexer } from './Lexer.js'; -import { Interval } from './misc/Interval.js'; +/* eslint-disable jsdoc/require-param, jsdoc/require-returns, @typescript-eslint/naming-convention */ +/* eslint-disable jsdoc/no-undefined-types */ + +import { Token } from "./Token.js"; +import { Lexer } from "./Lexer.js"; +import { Interval } from "./misc/Interval.js"; import { TokenStream } from "./TokenStream.js"; +import { TokenSource } from "./TokenSource.js"; +import { RuleContext } from "./RuleContext.js"; /** * This implementation of {@link TokenStream} loads tokens from a @@ -21,127 +26,129 @@ import { TokenStream } from "./TokenStream.js"; * {@link Token//HIDDEN_CHANNEL}, use a filtering token stream such a * {@link CommonTokenStream}.

      */ -export class BufferedTokenStream extends TokenStream { - constructor(tokenSource) { +export class BufferedTokenStream implements TokenStream { + /** + * The {@link TokenSource} from which tokens for this stream are fetched. + */ + protected tokenSource: TokenSource; - super(); - // The {@link TokenSource} from which tokens for this stream are fetched. - this.tokenSource = tokenSource; - /** - * A collection of all tokens fetched from the token source. The list is - * considered a complete view of the input once {@link //fetchedEOF} is set - * to {@code true}. - */ - this.tokens = []; + /** + * A collection of all tokens fetched from the token source. The list is + * considered a complete view of the input once {@link fetchedEOF} is set + * to `true`. + */ + protected tokens: Token[] = []; - /** - * The index into {@link //tokens} of the current token (next token to - * {@link //consume}). {@link //tokens}{@code [}{@link //p}{@code ]} should - * be - * {@link //LT LT(1)}. - * - *

      This field is set to -1 when the stream is first constructed or when - * {@link //setTokenSource} is called, indicating that the first token has - * not yet been fetched from the token source. For additional information, - * see the documentation of {@link IntStream} for a description of - * Initializing Methods.

      - */ - this._index = -1; - - /** - * Indicates whether the {@link Token//EOF} token has been fetched from - * {@link //tokenSource} and added to {@link //tokens}. This field improves - * performance for the following cases: - * - *
        - *
      • {@link //consume}: The lookahead check in {@link //consume} to - * prevent - * consuming the EOF symbol is optimized by checking the values of - * {@link //fetchedEOF} and {@link //p} instead of calling {@link - * //LA}.
      • - *
      • {@link //fetch}: The check to prevent adding multiple EOF symbols - * into - * {@link //tokens} is trivial with this field.
      • - *
          - */ - this.fetchedEOF = false; + /** + * The index into {@link tokens} of the current token (next token to + * {@link consume}). {@link tokens}`[p]` should be + * {@link LT LT(1)}. + * + *

          This field is set to -1 when the stream is first constructed or when + * {@link setTokenSource} is called, indicating that the first token has + * not yet been fetched from the token source. For additional information, + * see the documentation of {@link IntStream} for a description of + * Initializing Methods.

          + */ + protected p = -1; + + /** + * Indicates whether the {@link Token.EOF} token has been fetched from + * {@link tokenSource} and added to {@link tokens}. This field improves + * performance for the following cases: + * + *
            + *
          • {@link consume}: The lookahead check in {@link consume} to prevent + * consuming the EOF symbol is optimized by checking the values of + * {@link fetchedEOF} and {@link p} instead of calling {@link LA}.
          • + *
          • {@link fetch}: The check to prevent adding multiple EOF symbols into + * {@link tokens} is trivial with this field.
          • + *
              + */ + protected fetchedEOF = false; + + public constructor(tokenSource: TokenSource) { + this.tokenSource = tokenSource; } - mark() { + public mark(): number { return 0; } - release(marker) { + public release(_marker: number): void { // no resources to release } - reset() { + public reset(): void { this.seek(0); } - seek(index) { + public seek(index: number): void { this.lazyInit(); - this._index = this.adjustSeekIndex(index); + this.p = this.adjustSeekIndex(index); } - get size() { + public get size(): number { return this.tokens.length; } - get index() { - return this._index; + public get index(): number { + return this.p; } - get(index) { + public get(index: number): Token { this.lazyInit(); + return this.tokens[index]; } - consume() { + public consume(): void { let skipEofCheck = false; - if (this._index >= 0) { + if (this.p >= 0) { if (this.fetchedEOF) { // the last token in tokens is EOF. skip check if p indexes any // fetched token except the last. - skipEofCheck = this._index < this.tokens.length - 1; + skipEofCheck = this.p < this.tokens.length - 1; } else { // no EOF token in tokens. skip check if p indexes a fetched token. - skipEofCheck = this._index < this.tokens.length; + skipEofCheck = this.p < this.tokens.length; } } else { // not yet initialized skipEofCheck = false; } if (!skipEofCheck && this.LA(1) === Token.EOF) { - throw "cannot consume EOF"; + throw new Error("cannot consume EOF"); } - if (this.sync(this._index + 1)) { - this._index = this.adjustSeekIndex(this._index + 1); + if (this.sync(this.p + 1)) { + this.p = this.adjustSeekIndex(this.p + 1); } } /** * Make sure index {@code i} in tokens has a token. * - * @return {Boolean} {@code true} if a token is located at index {@code i}, otherwise + * @returns {boolean} {@code true} if a token is located at index {@code i}, otherwise * {@code false}. * @see //get(int i) */ - sync(i) { + public sync(i: number): boolean { const n = i - this.tokens.length + 1; // how many more elements we need? if (n > 0) { const fetched = this.fetch(n); + return fetched >= n; } + return true; } /** * Add {@code n} elements to buffer. * - * @return {Number} The actual number of elements added to the buffer. + * @returns {number} The actual number of elements added to the buffer. */ - fetch(n) { + public fetch(n: number): number { if (this.fetchedEOF) { return 0; } @@ -151,20 +158,23 @@ export class BufferedTokenStream extends TokenStream { this.tokens.push(t); if (t.type === Token.EOF) { this.fetchedEOF = true; + return i + 1; } } + return n; } - // Get all tokens from start..stop inclusively/// - getTokens(start, stop, types) { + // Get all tokens from start..stop, inclusively. + public getTokens(start?: number, stop?: number, types?: Set): Token[] { this.lazyInit(); if (start === undefined && stop === undefined) { return this.tokens; } + start ??= 0; if (stop === undefined) { stop = this.tokens.length - 1; } @@ -193,25 +203,27 @@ export class BufferedTokenStream extends TokenStream { break; } - if (types.contains(t.type)) { + if (types.has(t.type)) { subset.push(t); } } + return subset; } - LA(i) { - return this.LT(i).type; + public LA(i: number): number { + return this.LT(i)!.type; } - LB(k) { - if (this._index - k < 0) { + public LB(k: number): Token | null { + if (this.p - k < 0) { return null; } - return this.tokens[this._index - k]; + + return this.tokens[this.p - k]; } - LT(k) { + public LT(k: number): Token | null { this.lazyInit(); if (k === 0) { return null; @@ -219,12 +231,13 @@ export class BufferedTokenStream extends TokenStream { if (k < 0) { return this.LB(-k); } - const i = this._index + k - 1; + const i = this.p + k - 1; this.sync(i); if (i >= this.tokens.length) { // return EOF token // EOF must be last token return this.tokens[this.tokens.length - 1]; } + return this.tokens[i]; } @@ -239,33 +252,33 @@ export class BufferedTokenStream extends TokenStream { * that * the seek target is always an on-channel token.

              * - * @param {Number} i The target token index. - * @return {Number} The adjusted target token index. + * @param {number} i The target token index. + * @returns {number} The adjusted target token index. */ - adjustSeekIndex(i) { + public adjustSeekIndex(i: number): number { return i; } - lazyInit() { - if (this._index === -1) { + public lazyInit(): void { + if (this.p === -1) { this.setup(); } } - setup() { + public setup(): void { this.sync(0); - this._index = this.adjustSeekIndex(0); + this.p = this.adjustSeekIndex(0); } // Reset this token stream by setting its token source./// - setTokenSource(tokenSource) { + public setTokenSource(tokenSource: TokenSource): void { this.tokenSource = tokenSource; this.tokens = []; - this._index = -1; + this.p = -1; this.fetchedEOF = false; } - getTokenSource() { + public getTokenSource(): TokenSource { return this.tokenSource; } @@ -274,13 +287,13 @@ export class BufferedTokenStream extends TokenStream { * Return i if tokens[i] is on channel. Return -1 if there are no tokens * on channel between i and EOF. */ - nextTokenOnChannel(i, channel) { + public nextTokenOnChannel(i: number, channel: number): number { this.sync(i); if (i >= this.tokens.length) { return -1; } let token = this.tokens[i]; - while (token.channel !== this.channel) { + while (token.channel !== channel) { if (token.type === Token.EOF) { return -1; } @@ -288,6 +301,7 @@ export class BufferedTokenStream extends TokenStream { this.sync(i); token = this.tokens[i]; } + return i; } @@ -296,10 +310,11 @@ export class BufferedTokenStream extends TokenStream { * Return i if tokens[i] is on channel. Return -1 if there are no tokens * on channel between i and 0. */ - previousTokenOnChannel(i, channel) { + public previousTokenOnChannel(i: number, channel: number): number { while (i >= 0 && this.tokens[i].channel !== channel) { i -= 1; } + return i; } @@ -308,20 +323,21 @@ export class BufferedTokenStream extends TokenStream { * the current token up until we see a token on DEFAULT_TOKEN_CHANNEL or * EOF. If channel is -1, find any non default channel token. */ - getHiddenTokensToRight(tokenIndex, - channel) { + public getHiddenTokensToRight(tokenIndex: number, channel: number): Token[] | null { if (channel === undefined) { channel = -1; } this.lazyInit(); if (tokenIndex < 0 || tokenIndex >= this.tokens.length) { - throw "" + tokenIndex + " not in 0.." + this.tokens.length - 1; + throw new Error(`${tokenIndex} not in 0..${this.tokens.length - 1}`); } const nextOnChannel = this.nextTokenOnChannel(tokenIndex + 1, Lexer.DEFAULT_TOKEN_CHANNEL); - const from_ = tokenIndex + 1; - // if none onchannel to right, nextOnChannel=-1 so set to = last token + const from = tokenIndex + 1; + + // if none on-channel to right, nextOnChannel=-1 so set to = last token const to = nextOnChannel === -1 ? this.tokens.length - 1 : nextOnChannel; - return this.filterForChannel(from_, to, channel); + + return this.filterForChannel(from, to, channel); } /** @@ -329,26 +345,27 @@ export class BufferedTokenStream extends TokenStream { * the current token up until we see a token on DEFAULT_TOKEN_CHANNEL. * If channel is -1, find any non default channel token. */ - getHiddenTokensToLeft(tokenIndex, - channel) { + public getHiddenTokensToLeft(tokenIndex: number, channel: number): Token[] | null { if (channel === undefined) { channel = -1; } this.lazyInit(); if (tokenIndex < 0 || tokenIndex >= this.tokens.length) { - throw "" + tokenIndex + " not in 0.." + this.tokens.length - 1; + throw new Error(`${tokenIndex} not in 0..${this.tokens.length - 1}`); } const prevOnChannel = this.previousTokenOnChannel(tokenIndex - 1, Lexer.DEFAULT_TOKEN_CHANNEL); if (prevOnChannel === tokenIndex - 1) { return null; } + // if none on channel to left, prevOnChannel=-1 then from=0 - const from_ = prevOnChannel + 1; + const from = prevOnChannel + 1; const to = tokenIndex - 1; - return this.filterForChannel(from_, to, channel); + + return this.filterForChannel(from, to, channel); } - filterForChannel(left, right, channel) { + public filterForChannel(left: number, right: number, channel: number): Token[] | null { const hidden = []; for (let i = left; i < right + 1; i++) { const t = this.tokens[i]; @@ -363,48 +380,77 @@ export class BufferedTokenStream extends TokenStream { if (hidden.length === 0) { return null; } + return hidden; } - getSourceName() { + public getSourceName(): string { return this.tokenSource.sourceName; } - // Get the text of all tokens in this buffer./// - getText(interval) { - this.lazyInit(); - this.fill(); - if (!interval) { - interval = new Interval(0, this.tokens.length - 1); - } - let start = interval.start; - if (start instanceof Token) { - start = start.tokenIndex; - } - let stop = interval.stop; - if (stop instanceof Token) { - stop = stop.tokenIndex; - } - if (start === null || stop === null || start < 0 || stop < 0) { - return ""; - } - if (stop >= this.tokens.length) { - stop = this.tokens.length - 1; - } - let s = ""; - for (let i = start; i < stop + 1; i++) { - const t = this.tokens[i]; - if (t.type === Token.EOF) { - break; + /** Get the text of all tokens in this buffer. */ + public getText(): string; + public getText(interval: Interval): string; + public getText(ctx: RuleContext): string; + public getText(start: Token, stop: Token): string; + public getText(...args: unknown[]): string { + switch (args.length) { + case 0: { + return this.getText(Interval.of(0, this.size - 1)); + } + + case 1: { + if (args[0] instanceof Interval) { + const interval = args[0]; + const start = interval.start; + let stop = interval.stop; + if (start < 0 || stop < 0) { + return ""; + } + + this.sync(stop); + if (stop >= this.tokens.length) { + stop = this.tokens.length - 1; + } + + let buf = ""; + for (let i: number = start; i <= stop; i++) { + const t = this.tokens[i]; + if (t.type === Token.EOF) { + break; + } + + buf += t.text; + } + + return buf.toString(); + } else { + const ctx = args[0] as RuleContext; + + return this.getText(ctx.getSourceInterval()); + } + } + + case 2: { + const start = args[0] as Token; + const stop = args[1] as Token; + + if (start !== null && stop !== null) { + return this.getText(Interval.of(start.tokenIndex, stop.tokenIndex)); + } + + return ""; + } + + default: { + throw new Error(`Invalid number of arguments`); } - s = s + t.text; } - return s; } - // Get all tokens from lexer until EOF/// - fill() { + // Get all tokens from lexer until EOF. + public fill(): void { this.lazyInit(); - while (this.fetch(1000) === 1000); + while (this.fetch(1000) === 1000) { ; } } } diff --git a/src/CharStream.d.ts b/src/CharStream.d.ts deleted file mode 100644 index 205e5d9..0000000 --- a/src/CharStream.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntStream } from "./IntStream.js"; -import { Interval } from "./misc/Interval.js"; - -/** - * A source of characters for an ANTLR lexer. - * - * Note: CharStream is an interface in Java, but uses the CharStream class in JavaScript. - */ -export declare interface CharStream extends IntStream { - /** - * This method returns the text for a range of characters within this input - * stream. This method is guaranteed to not throw an exception if the - * specified {@code interval} lies entirely within a marked range. For more - * information about marked ranges, see {@link IntStream#mark}. - * - * @param interval an interval within the stream - * @returns the text of the specified interval - * - * @throws NullPointerException if {@code interval} is {@code null} - * @throws IllegalArgumentException if {@code interval.a < 0}, or if - * {@code interval.b < interval.a - 1}, or if {@code interval.b} lies at or - * past the end of the stream - * @throws UnsupportedOperationException if the stream does not support - * getting the text of the specified interval - */ - getText(interval: Interval): string; - getText(start: number, stop: number): string; - - reset(): void; - toString(): string; -} diff --git a/src/CharStream.js b/src/CharStream.ts similarity index 56% rename from src/CharStream.js rename to src/CharStream.ts index f6c1e79..917929e 100644 --- a/src/CharStream.js +++ b/src/CharStream.ts @@ -4,7 +4,9 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from './Token.js'; +/* eslint-disable jsdoc/require-param, @typescript-eslint/naming-convention */ + +import { Token } from "./Token.js"; import { Interval } from "./misc/Interval.js"; import { IntStream } from "./IntStream.js"; @@ -15,17 +17,23 @@ import { IntStream } from "./IntStream.js"; * Otherwise, the input is treated as a series of 16-bit UTF-16 code * units. */ -export class CharStream { - constructor(data, decodeToUnicodeCodePoints) { - this.name = ""; +// TODO: CharStream should be an interface, not a class. +export class CharStream implements IntStream { + public name = ""; + public index = 0; + + private stringData: string; + private data: number[]; + private decodeToUnicodeCodePoints: boolean; + + public constructor(data: string, decodeToUnicodeCodePoints = false) { this.stringData = data; - this.decodeToUnicodeCodePoints = decodeToUnicodeCodePoints ?? false; + this.decodeToUnicodeCodePoints = decodeToUnicodeCodePoints; - this._index = 0; this.data = []; if (this.decodeToUnicodeCodePoints) { for (let i = 0; i < this.stringData.length;) { - const codePoint = this.stringData.codePointAt(i); + const codePoint = this.stringData.codePointAt(i)!; this.data.push(codePoint); i += codePoint <= 0xFFFF ? 1 : 2; } @@ -35,7 +43,6 @@ export class CharStream { this.data[i] = this.stringData.charCodeAt(i); } } - this._size = this.data.length; } /** @@ -43,94 +50,97 @@ export class CharStream { * when the object was created *except* the data array is not * touched. */ - reset() { - this._index = 0; + public reset(): void { + this.index = 0; } - consume() { - if (this._index >= this._size) { - // assert this.LA(1) == Token.EOF - throw ("cannot consume EOF"); + public consume(): void { + if (this.index >= this.data.length) { + throw new Error("cannot consume EOF"); } - this._index += 1; + this.index += 1; } - LA(offset) { + public LA(offset: number): number { if (offset === 0) { return 0; // undefined } if (offset < 0) { offset += 1; // e.g., translate LA(-1) to use offset=0 } - const pos = this._index + offset - 1; - if (pos < 0 || pos >= this._size) { // invalid + const pos = this.index + offset - 1; + if (pos < 0 || pos >= this.data.length) { // invalid return Token.EOF; } + return this.data[pos]; } // mark/release do nothing; we have entire buffer - mark() { + public mark(): number { return -1; } - release(marker) { + public release(_marker: number): void { } /** * consume() ahead until p==_index; can't just set p=_index as we must * update line and column. If we seek backwards, just set p */ - seek(_index) { - if (_index <= this._index) { - this._index = _index; // just jump; don't update stream state (line, + public seek(index: number): void { + if (index <= this.index) { + this.index = index; // just jump; don't update stream state (line, + // ...) return; } // seek forward - this._index = Math.min(_index, this._size); + this.index = Math.min(index, this.data.length); } - getText(intervalOrStart, stop) { - let start; + public getText(start: number, stop: number): string; + public getText(interval: Interval): string; + public getText(intervalOrStart: Interval | number, stop?: number): string { + let begin; + let end: number; if (intervalOrStart instanceof Interval) { - start = intervalOrStart.start; - stop = intervalOrStart.stop; + begin = intervalOrStart.start; + end = intervalOrStart.stop; } else { - start = intervalOrStart; + begin = intervalOrStart; + end = stop ?? this.data.length - 1; } - if (stop >= this._size) { - stop = this._size - 1; + if (end >= this.data.length) { + end = this.data.length - 1; } - if (start >= this._size) { + + if (begin >= this.data.length) { return ""; } else { if (this.decodeToUnicodeCodePoints) { let result = ""; - for (let i = start; i <= stop; i++) { + for (let i = begin; i <= end; i++) { result += String.fromCodePoint(this.data[i]); } + return result; } else { - return this.stringData.slice(start, stop + 1); + return this.stringData.slice(begin, end + 1); } } } - toString() { + public toString(): string { return this.stringData; } - get index() { - return this._index; - } - - get size() { - return this._size; + public get size(): number { + return this.data.length; } - getSourceName() { + public getSourceName(): string { if (this.name) { return this.name; } else { diff --git a/src/CharStreams.d.ts b/src/CharStreams.d.ts deleted file mode 100644 index a8d76c3..0000000 --- a/src/CharStreams.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from "./CharStream.js"; - -export declare class CharStreams { - public static fromString(data: string, decodeToUnicodeCodePoints?: boolean): CharStream; - public static fromBuffer(buffer: Buffer, encoding?: string): CharStream; - public static fromBlob(blob: Blob, encoding: string, onLoad: (stream: CharStream) => void, - onError: (error: Error) => void): void; - public static fromPath(path: string, encoding: string, callback: (err: Error, stream: CharStream) => void): void; - public static fromPathSync(path: string, encoding: string): CharStream; -} diff --git a/src/CharStreams.js b/src/CharStreams.js deleted file mode 100644 index 575b834..0000000 --- a/src/CharStreams.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -// TS thinks this is an unused interface import, but instead we are importing the same named class here. -import { CharStream } from "./CharStream.js"; - -/** - * Utility functions to create InputStreams from various sources. - * - * All returned InputStreams support the full range of Unicode - * up to U+10FFFF (the default behavior of InputStream only supports - * code points up to U+FFFF). - */ -export const CharStreams = { - // Creates an InputStream from a string. - fromString: function (str) { - return new CharStream(str, true); - }, - - /** - * Asynchronously creates an InputStream from a blob given the - * encoding of the bytes in that blob (defaults to 'utf8' if - * encoding is null). - * - * Invokes onLoad(result) on success, onError(error) on - * failure. - */ - fromBlob: function (blob, encoding, onLoad, onError) { - const reader = new window.FileReader(); - reader.onload = function (e) { - const is = new CharStream(e.target.result, true); - onLoad(is); - }; - reader.onerror = onError; - reader.readAsText(blob, encoding); - }, - - /** - * Creates an InputStream from a Buffer given the - * encoding of the bytes in that buffer (defaults to 'utf8' if - * encoding is null). - */ - fromBuffer: function (buffer, encoding) { - return new CharStream(buffer.toString(encoding), true); - }, -}; diff --git a/src/CharStreams.ts b/src/CharStreams.ts new file mode 100644 index 0000000..f911f25 --- /dev/null +++ b/src/CharStreams.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable jsdoc/require-param, jsdoc/require-returns */ + +import { CharStream } from "./CharStream.js"; + +/** + * Utility functions to create Character streams from various sources. + * + * All returned InputStreams support the full range of Unicode + * up to U+10FFFF (the default behavior of InputStream only supports + * code points up to U+FFFF). + */ +// TODO: remove this class, as it does not provide any value compared to CharStream. +// Java CharStreams class is a factory for CharStream instances from various sources (files, channels and whatnot). +// In TypeScript we always work with strings as input and let the application take care to load them. +export class CharStreams { + // Creates an CharStream from a string. + public static fromString(str: string): CharStream { + return new CharStream(str, true); + } + + /** + * Creates an CharStream from a Buffer given the + * encoding of the bytes in that buffer (defaults to 'utf8' if + * encoding is null). + */ + public static fromBuffer(buffer: Buffer, encoding?: BufferEncoding): CharStream { + return new CharStream(buffer.toString(encoding), true); + } +} diff --git a/src/CommonToken.d.ts b/src/CommonToken.d.ts deleted file mode 100644 index f9c872a..0000000 --- a/src/CommonToken.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { InputStream } from "./InputStream.js"; -import { Recognizer } from "./Recognizer.js"; -import { Token } from "./Token.js"; -import { TokenSource } from "./TokenSource.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; - -export declare class CommonToken extends Token { - // eslint-disable-next-line @typescript-eslint/naming-convention - public readonly EMPTY_SOURCE: [TokenSource | null, InputStream | null]; - - public readonly source: [TokenSource | null, InputStream | null]; - - public constructor(source: [TokenSource | null, InputStream | null], type: number, channel: number, start: number, - stop: number); - - public clone(): CommonToken; - public cloneWithType(type: number): CommonToken; - - public toString(recognizer?: Recognizer): string; -} diff --git a/src/CommonToken.js b/src/CommonToken.js deleted file mode 100644 index e53c70b..0000000 --- a/src/CommonToken.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Token } from "./Token.js"; - -export class CommonToken extends Token { - constructor(source, type, channel, start, stop) { - super(); - this.source = source !== undefined ? source : CommonToken.EMPTY_SOURCE; - this.type = type !== undefined ? type : null; - this.channel = channel !== undefined ? channel : Token.DEFAULT_CHANNEL; - this.start = start !== undefined ? start : -1; - this.stop = stop !== undefined ? stop : -1; - this.tokenIndex = -1; - if (this.source[0] !== null) { - this.line = source[0].line; - this.column = source[0].column; - } else { - this.column = -1; - } - } - - /** - * Constructs a new {@link CommonToken} as a copy of another {@link Token}. - * - *

              - * If {@code oldToken} is also a {@link CommonToken} instance, the newly - * constructed token will share a reference to the {@link //text} field and - * the {@link Pair} stored in {@link //source}. Otherwise, {@link //text} will - * be assigned the result of calling {@link //getText}, and {@link //source} - * will be constructed from the result of {@link Token//getTokenSource} and - * {@link Token//getInputStream}.

              - * - * @param oldToken The token to copy. - */ - clone() { - const t = new CommonToken(this.source, this.type, this.channel, this.start, this.stop); - t.tokenIndex = this.tokenIndex; - t.line = this.line; - t.column = this.column; - t.text = this.text; - return t; - } - - cloneWithType(type) { - const t = new CommonToken(this.source, type, this.channel, this.start, this.stop); - t.tokenIndex = this.tokenIndex; - t.line = this.line; - t.column = this.column; - if (type === Token.EOF) - t.text = ""; - return t; - } - - toString(recognizer) { - let channelStr = ""; - if (this._channel > 0) { - channelStr = ",channel=" + this.channel; - } - - let text = this.text; - if (text) { - text = text.replace(/\n/g, "\\n"); - text = text.replace(/\r/g, "\\r"); - text = text.replace(/\t/g, "\\t"); - } else { - text = ""; - } - - let typeString = String(this.type); - if (recognizer) { - typeString = recognizer.vocabulary.getDisplayName(this.type); - } - - return "[@" + this.tokenIndex + "," + this.start + ":" + this.stop + "='" + text + "',<" + typeString + ">" + - channelStr + "," + this.line + ":" + this.column + "]"; - } - - get text() { - if (this._text !== null) { - return this._text; - } - const input = this.getInputStream(); - if (input === null) { - return null; - } - const n = input.size; - if (this.start < n && this.stop < n) { - return input.getText(this.start, this.stop); - } else { - return ""; - } - } - - set text(text) { - this._text = text; - } -} - -/** - * An empty {@link Pair} which is used as the default value of - * {@link //source} for tokens that do not have a source. - */ -CommonToken.EMPTY_SOURCE = [null, null]; diff --git a/src/CommonToken.ts b/src/CommonToken.ts new file mode 100644 index 0000000..b060379 --- /dev/null +++ b/src/CommonToken.ts @@ -0,0 +1,201 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable jsdoc/require-returns, no-underscore-dangle */ + +import { CharStream } from "./CharStream.js"; +import { Recognizer } from "./Recognizer.js"; +import { Token } from "./Token.js"; +import { TokenSource } from "./TokenSource.js"; +import { WritableToken } from "./WritableToken.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; + +export class CommonToken implements WritableToken { + /** + * An empty tuple which is used as the default value of + * {@link source} for tokens that do not have a source. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly EMPTY_SOURCE: [TokenSource | null, CharStream | null] = [null, null]; + + /** + * This is the backing field for {@link #getTokenSource} and + * {@link #getInputStream}. + * + *

              + * These properties share a field to reduce the memory footprint of + * {@link CommonToken}. Tokens created by a {@link CommonTokenFactory} from + * the same source and input stream share a reference to the same + * {@link Pair} containing these values.

              + */ + public source: [TokenSource | null, CharStream | null]; + + public tokenIndex = -1; + + public start = 0; + + public stop = 0; + + /** + * This is the backing field for {@link #getType} and {@link #setType}. + */ + public type = 0; + + /** + * This is the backing field for {@link #getLine} and {@link #setLine}. + */ + public line = 0; + + /** + * This is the backing field for {@link #getCharPositionInLine} and + * {@link #setCharPositionInLine}. + */ + public column = -1; // set to invalid position + + /** + * This is the backing field for {@link #getChannel} and + * {@link #setChannel}. + */ + public channel = Token.DEFAULT_CHANNEL; + + /** + * This is the backing field for {@link #getText} when the token text is + * explicitly set in the constructor or via {@link #setText}. + * + * @see #getText() + */ + #text: string | null = null; + + public constructor(source: [TokenSource | null, CharStream | null], type: number, channel: number, start: number, + stop: number) { + this.source = source; + this.type = type; + this.channel = channel ?? Token.DEFAULT_CHANNEL; + this.start = start; + this.stop = stop; + if (this.source[0] !== null) { + this.line = source[0]!.line; + // eslint-disable-next-line no-underscore-dangle + this.column = source[0]!._tokenStartColumn; + } else { + this.column = -1; + } + }; + + public get tokenSource(): TokenSource | null { + return this.source[0] ?? null; + } + + public get inputStream(): CharStream | null { + return this.source[1] ?? null; + } + + /** + * Constructs a new {@link CommonToken} as a copy of another {@link Token}. + * + *

              + * If {@code oldToken} is also a {@link CommonToken} instance, the newly + * constructed token will share a reference to the {@link text} field and + * the {@link Pair} stored in {@link source}. Otherwise, {@link text} will + * be assigned the result of calling {@link getText}, and {@link source} + * will be constructed from the result of {@link Token//getTokenSource} and + * {@link Token//getInputStream}.

              + */ + public clone(): CommonToken { + const t = new CommonToken(this.source, this.type, this.channel, this.start, this.stop); + t.tokenIndex = this.tokenIndex; + t.line = this.line; + t.column = this.column; + t.#text = this.#text; + + return t; + } + + public cloneWithType(type: number): CommonToken { + const t = new CommonToken(this.source, type, this.channel, this.start, this.stop); + t.tokenIndex = this.tokenIndex; + t.line = this.line; + t.column = this.column; + if (type === Token.EOF) { + t.#text = ""; + } + + return t; + } + + public toString(recognizer?: Recognizer): string { + let channelStr = ""; + if (this.channel > 0) { + channelStr = ",channel=" + this.channel; + } + + let text = this.text; + if (text) { + text = text.replace(/\n/g, "\\n"); + text = text.replace(/\r/g, "\\r"); + text = text.replace(/\t/g, "\\t"); + } else { + text = ""; + } + + let typeString = String(this.type); + if (recognizer) { + typeString = recognizer.vocabulary.getDisplayName(this.type) ?? ""; + } + + return "[@" + this.tokenIndex + "," + this.start + ":" + this.stop + "='" + text + "',<" + typeString + ">" + + channelStr + "," + this.line + ":" + this.column + "]"; + } + + public get text(): string | null { + if (this.#text !== null) { + return this.#text; + } + + const input = this.inputStream; + if (input === null) { + return null; + } + + const n = input.size; + if (this.start < n && this.stop < n) { + return input.getText(this.start, this.stop); + } else { + return ""; + } + } + + public set text(text: string) { + this.#text = text; + } + + // WritableToken implementation + + public setText(text: string | null): void { + this.#text = text; + } + + public setType(ttype: number): void { + this.type = ttype; + } + + public setLine(line: number): void { + this.line = line; + } + + public setCharPositionInLine(pos: number): void { + this.column = pos; + } + + public setChannel(channel: number): void { + this.channel = channel; + } + + public setTokenIndex(index: number): void { + this.tokenIndex = index; + } + +} diff --git a/src/CommonTokenFactory.js b/src/CommonTokenFactory.js deleted file mode 100644 index b958e48..0000000 --- a/src/CommonTokenFactory.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CommonToken } from './CommonToken.js'; -import { TokenFactory } from './TokenFactory.js'; - -/** - * This default implementation of {@link TokenFactory} creates - * {@link CommonToken} objects. - */ -export class CommonTokenFactory extends TokenFactory { - constructor(copyText) { - super(); - /** - * Indicates whether {@link CommonToken//setText} should be called after - * constructing tokens to explicitly set the text. This is useful for cases - * where the input stream might not be able to provide arbitrary substrings - * of text from the input after the lexer creates a token (e.g. the - * implementation of {@link CharStream//getText} in - * {@link UnbufferedCharStream} throws an - * {@link UnsupportedOperationException}). Explicitly setting the token text - * allows {@link Token//getText} to be called at any time regardless of the - * input stream implementation. - * - *

              - * The default value is {@code false} to avoid the performance and memory - * overhead of copying text for every token unless explicitly requested.

              - */ - this.copyText = copyText === undefined ? false : copyText; - } - - create(source, type, text, channel, start, stop, line, column) { - const t = new CommonToken(source, type, channel, start, stop); - t.line = line; - t.column = column; - if (text !== null) { - t.text = text; - } else if (this.copyText && source[1] !== null) { - t.text = source[1].getText(start, stop); - } - return t; - } - - createThin(type, text) { - const t = new CommonToken(null, type); - t.text = text; - return t; - } -} - -/** - * The default {@link CommonTokenFactory} instance. - * - *

              - * This token factory does not explicitly copy token text when constructing - * tokens.

              - */ -CommonTokenFactory.DEFAULT = new CommonTokenFactory(); diff --git a/src/CommonTokenFactory.ts b/src/CommonTokenFactory.ts new file mode 100644 index 0000000..c0d747b --- /dev/null +++ b/src/CommonTokenFactory.ts @@ -0,0 +1,76 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { CharStream } from "./CharStream.js"; +import { CommonToken } from "./CommonToken.js"; +import { TokenFactory } from "./TokenFactory.js"; +import { TokenSource } from "./TokenSource.js"; + +/** + * This default implementation of {@link TokenFactory} creates + * {@link CommonToken} objects. + */ +export class CommonTokenFactory implements TokenFactory { + /** + * The default {@link CommonTokenFactory} instance. + * + *

              + * This token factory does not explicitly copy token text when constructing + * tokens.

              + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly DEFAULT = new CommonTokenFactory(); + + /** + * Indicates whether {@link CommonToken#setText} should be called after + * constructing tokens to explicitly set the text. This is useful for cases + * where the input stream might not be able to provide arbitrary substrings + * of text from the input after the lexer creates a token (e.g. the + * implementation of {@link CharStream#getText} in + * {@link UnbufferedCharStream} throws an + * {@link UnsupportedOperationException}). Explicitly setting the token text + * allows {@link Token#getText} to be called at any time regardless of the + * input stream implementation. + * + *

              + * The default value is {@code false} to avoid the performance and memory + * overhead of copying text for every token unless explicitly requested.

              + */ + protected readonly copyText: boolean = false; + + public constructor(copyText?: boolean) { + /** + * Indicates whether {@link CommonToken//setText} should be called after + * constructing tokens to explicitly set the text. This is useful for cases + * where the input stream might not be able to provide arbitrary substrings + * of text from the input after the lexer creates a token (e.g. the + * implementation of {@link CharStream//getText} in + * {@link UnbufferedCharStream} throws an + * {@link UnsupportedOperationException}). Explicitly setting the token text + * allows {@link Token//getText} to be called at any time regardless of the + * input stream implementation. + * + *

              + * The default value is {@code false} to avoid the performance and memory + * overhead of copying text for every token unless explicitly requested.

              + */ + this.copyText = copyText ?? false; + } + + public create(source: [TokenSource | null, CharStream | null], type: number, text: string | null, channel: number, + start: number, stop: number, line: number, column: number): CommonToken { + const t = new CommonToken(source, type, channel, start, stop); + t.line = line; + t.column = column; + if (text !== null) { + t.text = text; + } else if (this.copyText && source[1] !== null) { + t.text = source[1].getText(start, stop); + } + + return t; + } +} diff --git a/src/CommonTokenStream.d.ts b/src/CommonTokenStream.d.ts deleted file mode 100644 index db8077f..0000000 --- a/src/CommonTokenStream.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Lexer } from "./Lexer.js"; -import { BufferedTokenStream } from "./BufferedTokenStream.js"; - -export declare class CommonTokenStream extends BufferedTokenStream { - public constructor(lexer: Lexer); - public constructor(lexer: Lexer, channel: number); -} diff --git a/src/CommonTokenStream.js b/src/CommonTokenStream.ts similarity index 69% rename from src/CommonTokenStream.js rename to src/CommonTokenStream.ts index d45875e..a485592 100644 --- a/src/CommonTokenStream.js +++ b/src/CommonTokenStream.ts @@ -4,8 +4,11 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from './Token.js'; -import { BufferedTokenStream } from './BufferedTokenStream.js'; +/* eslint-disable @typescript-eslint/naming-convention */ + +import { Token } from "./Token.js"; +import { BufferedTokenStream } from "./BufferedTokenStream.js"; +import { TokenSource } from "./TokenSource.js"; /** * This class extends {@link BufferedTokenStream} with functionality to filter @@ -14,9 +17,9 @@ import { BufferedTokenStream } from './BufferedTokenStream.js'; * *

              * This token stream provides access to all tokens by index or when calling - * methods like {@link //getText}. The channel filtering is only used for code - * accessing tokens via the lookahead methods {@link //LA}, {@link //LT}, and - * {@link //LB}.

              + * methods like {@link getText}. The channel filtering is only used for code + * accessing tokens via the lookahead methods {@link LA}, {@link LT}, and + * {@link LB}.

              * *

              * By default, tokens are placed on the default channel @@ -32,16 +35,25 @@ import { BufferedTokenStream } from './BufferedTokenStream.js'; * channel.

              */ export class CommonTokenStream extends BufferedTokenStream { - constructor(lexer, channel) { + /** + * Specifies the channel to use for filtering tokens. + * + *

              + * The default value is {@link Token#DEFAULT_CHANNEL}, which matches the + * default channel assigned to tokens created by the lexer.

              + */ + protected channel = Token.DEFAULT_CHANNEL; + + public constructor(lexer: TokenSource, channel?: number) { super(lexer); - this.channel = channel === undefined ? Token.DEFAULT_CHANNEL : channel; + this.channel = channel ?? Token.DEFAULT_CHANNEL; } - adjustSeekIndex(i) { + public override adjustSeekIndex(i: number): number { return this.nextTokenOnChannel(i, this.channel); } - LB(k) { + public override LB(k: number): Token | null { if (k === 0 || this.index - k < 0) { return null; } @@ -56,10 +68,11 @@ export class CommonTokenStream extends BufferedTokenStream { if (i < 0) { return null; } + return this.tokens[i]; } - LT(k) { + public override LT(k: number): Token | null { this.lazyInit(); if (k === 0) { return null; @@ -77,15 +90,15 @@ export class CommonTokenStream extends BufferedTokenStream { } n += 1; } + return this.tokens[i]; } // Count EOF just once. - getNumberOfOnChannelTokens() { + public getNumberOfOnChannelTokens(): number { let n = 0; this.fill(); - for (let i = 0; i < this.tokens.length; i++) { - const t = this.tokens[i]; + for (const t of this.tokens) { if (t.channel === this.channel) { n += 1; } @@ -93,6 +106,7 @@ export class CommonTokenStream extends BufferedTokenStream { break; } } + return n; } } diff --git a/src/ConsoleErrorListener.js b/src/ConsoleErrorListener.js deleted file mode 100644 index 31224c0..0000000 --- a/src/ConsoleErrorListener.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BaseErrorListener } from "./BaseErrorListener.js"; - -/** - * {@inheritDoc} - * - *

              - * This implementation prints messages to {@link System//err} containing the - * values of {@code line}, {@code charPositionInLine}, and {@code msg} using - * the following format.

              - * - *
              - * line line:charPositionInLine msg
              - * 
              - * - */ -export class ConsoleErrorListener extends BaseErrorListener { - constructor() { - super(); - } - - syntaxError(recognizer, offendingSymbol, line, column, msg, e) { - console.error("line " + line + ":" + column + " " + msg); - } -} - - -/** - * Provides a default instance of {@link ConsoleErrorListener}. - */ -ConsoleErrorListener.INSTANCE = new ConsoleErrorListener(); diff --git a/src/ConsoleErrorListener.ts b/src/ConsoleErrorListener.ts new file mode 100644 index 0000000..4b517fa --- /dev/null +++ b/src/ConsoleErrorListener.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { BaseErrorListener } from "./BaseErrorListener.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { Recognizer } from "./Recognizer.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; + +/** + * {@inheritDoc} + * + *

              + * This implementation prints messages to {@link System//err} containing the + * values of {@code line}, {@code charPositionInLine}, and {@code msg} using + * the following format.

              + * + *
              + * line line:charPositionInLine msg
              + * 
              + * + */ +export class ConsoleErrorListener extends BaseErrorListener { + /** + * Provides a default instance of {@link ConsoleErrorListener}. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly INSTANCE = new ConsoleErrorListener(); + + public override syntaxError(recognizer: Recognizer | null, + offendingSymbol: unknown, + line: number, + charPositionInLine: number, + msg: string | null, + _e: RecognitionException | null): void { + console.error("line " + line + ":" + charPositionInLine + " " + msg); + } +} diff --git a/src/DefaultErrorStrategy.d.ts b/src/DefaultErrorStrategy.d.ts deleted file mode 100644 index 9005060..0000000 --- a/src/DefaultErrorStrategy.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ANTLRErrorStrategy } from "./ANTLRErrorStrategy.js"; -import { RecognitionException } from "./RecognitionException.js"; -import { Parser } from "./Parser.js"; -import { Token } from "./Token.js"; - -export class DefaultErrorStrategy implements ANTLRErrorStrategy { - public recover(recognizer: Parser, e: RecognitionException): void; - public recoverInline(recognizer: Parser): Token; - public reportError(recognizer: Parser, e: RecognitionException): void; - public reportMatch(recognizer: Parser): void; - public reset(recognizer: Parser): void; - public sync(recognizer: Parser): void; - public inErrorRecoveryMode(recognizer: Parser): void; - public beginErrorCondition(recognizer: Parser): void; - public getMissingSymbol(recognizer: Parser): Token; -} diff --git a/src/DefaultErrorStrategy.js b/src/DefaultErrorStrategy.ts similarity index 79% rename from src/DefaultErrorStrategy.js rename to src/DefaultErrorStrategy.ts index d42b4a5..496b7fb 100644 --- a/src/DefaultErrorStrategy.js +++ b/src/DefaultErrorStrategy.ts @@ -4,48 +4,67 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable jsdoc/require-returns, jsdoc/require-param */ + import { FailedPredicateException } from "./FailedPredicateException.js"; import { InputMismatchException } from "./InputMismatchException.js"; import { NoViableAltException } from "./NoViableAltException.js"; import { ATNState } from "./atn/ATNState.js"; -import { Token } from './Token.js'; +import { Token } from "./Token.js"; import { Interval } from "./misc/Interval.js"; import { IntervalSet } from "./misc/IntervalSet.js"; import { ATNStateType } from "./atn/ATNStateType.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; +import { Parser } from "./Parser.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { CommonToken } from "./CommonToken.js"; +import { RuleTransition } from "./atn/RuleTransition.js"; /** * This is the default implementation of {@link ANTLRErrorStrategy} used for * error reporting and recovery in ANTLR parsers. */ export class DefaultErrorStrategy { - constructor() { - /** - * Indicates whether the error strategy is currently "recovering from an - * error". This is used to suppress reporting multiple error messages while - * attempting to recover from a detected syntax error. - * - * @see //inErrorRecoveryMode - */ - this.errorRecoveryMode = false; + /** + * Indicates whether the error strategy is currently "recovering from an + * error". This is used to suppress reporting multiple error messages while + * attempting to recover from a detected syntax error. + * + * @see #inErrorRecoveryMode + */ + protected errorRecoveryMode = false; - /** - * The index into the input stream where the last error occurred. - * This is used to prevent infinite loops where an error is found - * but no token is consumed during recovery...another error is found, - * ad nauseam. This is a failsafe mechanism to guarantee that at least - * one token/tree node is consumed for two errors. - */ - this.lastErrorIndex = -1; - this.lastErrorStates = null; - this.nextTokensContext = null; - this.nextTokenState = 0; - } + /** + * The index into the input stream where the last error occurred. + * This is used to prevent infinite loops where an error is found + * but no token is consumed during recovery...another error is found, + * ad nauseam. This is a failsafe mechanism to guarantee that at least + * one token/tree node is consumed for two errors. + */ + protected lastErrorIndex = -1; + + protected lastErrorStates = new IntervalSet(); /** - *

              The default implementation simply calls {@link //endErrorCondition} to + * This field is used to propagate information about the lookahead following + * the previous match. Since prediction prefers completing the current rule + * to error recovery efforts, error reporting may occur later than the + * original point where it was discoverable. The original context is used to + * compute the true expected sets as though the reporting occurred as early + * as possible. + */ + protected nextTokensContext: ParserRuleContext | null = null; + + /** + * @see #nextTokensContext + */ + protected nextTokenState = 0; + + /** + *

              The default implementation simply calls {@link endErrorCondition} to * ensure that the handler is not in error recovery mode.

              */ - reset(recognizer) { + public reset(recognizer: Parser): void { this.endErrorCondition(recognizer); } @@ -53,32 +72,31 @@ export class DefaultErrorStrategy { * This method is called to enter error recovery mode when a recognition * exception is reported. * - * @param recognizer the parser instance + * @param _recognizer the parser instance */ - beginErrorCondition(recognizer) { + public beginErrorCondition(_recognizer: Parser): void { this.errorRecoveryMode = true; } - inErrorRecoveryMode(recognizer) { + public inErrorRecoveryMode(_recognizer: Parser): boolean { return this.errorRecoveryMode; } /** * This method is called to leave error recovery mode after recovering from * a recognition exception. - * @param recognizer */ - endErrorCondition(recognizer) { + public endErrorCondition(_recognizer: Parser): void { this.errorRecoveryMode = false; - this.lastErrorStates = null; + this.lastErrorStates = new IntervalSet(); this.lastErrorIndex = -1; } /** * {@inheritDoc} - *

              The default implementation simply calls {@link //endErrorCondition}.

              + *

              The default implementation simply calls {@link endErrorCondition}.

              */ - reportMatch(recognizer) { + public reportMatch(recognizer: Parser): void { this.endErrorCondition(recognizer); } @@ -86,22 +104,22 @@ export class DefaultErrorStrategy { * {@inheritDoc} * *

              The default implementation returns immediately if the handler is already - * in error recovery mode. Otherwise, it calls {@link //beginErrorCondition} + * in error recovery mode. Otherwise, it calls {@link beginErrorCondition} * and dispatches the reporting task based on the runtime type of {@code e} * according to the following table.

              * *
                *
              • {@link NoViableAltException}: Dispatches the call to - * {@link //reportNoViableAlternative}
              • + * {@link reportNoViableAlternative} *
              • {@link InputMismatchException}: Dispatches the call to - * {@link //reportInputMismatch}
              • + * {@link reportInputMismatch} *
              • {@link FailedPredicateException}: Dispatches the call to - * {@link //reportFailedPredicate}
              • + * {@link reportFailedPredicate} *
              • All other types: calls {@link Parser//notifyErrorListeners} to report * the exception
              • *
              */ - reportError(recognizer, e) { + public reportError(recognizer: Parser, e: RecognitionException): void { // if we've already reported an error and have not matched a token // yet successfully, don't report any errors. if (this.inErrorRecoveryMode(recognizer)) { @@ -116,8 +134,7 @@ export class DefaultErrorStrategy { this.reportFailedPredicate(recognizer, e); } else { console.log("unknown recognition error type: " + e.constructor.name); - console.log(e.stack); - recognizer.notifyErrorListeners(e.offendingToken, e.getMessage(), e); + recognizer.notifyErrorListeners(e.message, e.offendingToken, e); } } @@ -130,20 +147,18 @@ export class DefaultErrorStrategy { * that can follow the current rule.

              * */ - recover(recognizer, e) { - if (this.lastErrorIndex === recognizer.inputStream.index && - this.lastErrorStates !== null && this.lastErrorStates.indexOf(recognizer.state) >= 0) { + public recover(recognizer: Parser, _e: RecognitionException): void { + if (this.lastErrorIndex === recognizer.inputStream.index && this.lastErrorStates.contains(recognizer.state)) { // uh oh, another error at same token index and previously-visited // state in ATN; must be a case where LT(1) is in the recovery // token set so nothing got consumed. Consume a single token // at least to prevent an infinite loop; this is a failsafe. recognizer.consume(); } - this.lastErrorIndex = recognizer._input.index; - if (this.lastErrorStates === null) { - this.lastErrorStates = []; - } - this.lastErrorStates.push(recognizer.state); + + this.lastErrorIndex = recognizer.inputStream.index; + + this.lastErrorStates.addOne(recognizer.state); const followSet = this.getErrorRecoverySet(recognizer); this.consumeUntil(recognizer, followSet); } @@ -162,7 +177,7 @@ export class DefaultErrorStrategy { * sync : {consume to what can follow sync} ; * * - * At the start of a sub rule upon error, {@link //sync} performs single + * At the start of a sub rule upon error, {@link sync} performs single * token deletion, if possible. If it can't do that, it bails on the current * rule and uses the default error recovery, which consumes until the * resynchronization set of the current rule. @@ -195,26 +210,28 @@ export class DefaultErrorStrategy { * functionality by simply overriding this method as a blank { }.

              * */ - sync(recognizer) { + public sync(recognizer: Parser): void { // If already recovering, don't try to sync if (this.inErrorRecoveryMode(recognizer)) { return; } - const s = recognizer.interpreter.atn.states[recognizer.state]; + const s = recognizer.atn.states[recognizer.state]!; const la = recognizer.tokenStream.LA(1); // try cheaper subset first; might get lucky. seems to shave a wee bit off const nextTokens = recognizer.atn.nextTokens(s); if (nextTokens.contains(la)) { this.nextTokensContext = null; this.nextTokenState = ATNState.INVALID_STATE_NUMBER; + return; } else if (nextTokens.contains(Token.EPSILON)) { if (this.nextTokensContext === null) { // It's possible the next token won't match information tracked // by sync is restricted for performance. - this.nextTokensContext = recognizer._ctx; - this.nextTokensState = recognizer._stateNumber; + this.nextTokensContext = recognizer.context; + this.nextTokenState = recognizer.state; } + return; } switch (s.stateType) { @@ -244,7 +261,7 @@ export class DefaultErrorStrategy { } /** - * This is called by {@link //reportError} when the exception is a + * This is called by {@link reportError} when the exception is a * {@link NoViableAltException}. * * @see //reportError @@ -252,14 +269,14 @@ export class DefaultErrorStrategy { * @param recognizer the parser instance * @param e the recognition exception */ - reportNoViableAlternative(recognizer, e) { + public reportNoViableAlternative(recognizer: Parser, e: NoViableAltException): void { const tokens = recognizer.tokenStream; let input; - if (tokens !== null) { + if (tokens !== null && e.startToken) { if (e.startToken.type === Token.EOF) { input = ""; } else { - input = tokens.getText(new Interval(e.startToken.tokenIndex, e.offendingToken.tokenIndex)); + input = tokens.getText(new Interval(e.startToken.tokenIndex, e.offendingToken!.tokenIndex)); } } else { input = ""; @@ -269,7 +286,7 @@ export class DefaultErrorStrategy { } /** - * This is called by {@link //reportError} when the exception is an + * This is called by {@link reportError} when the exception is an * {@link InputMismatchException}. * * @see //reportError @@ -277,14 +294,14 @@ export class DefaultErrorStrategy { * @param recognizer the parser instance * @param e the recognition exception */ - reportInputMismatch(recognizer, e) { + public reportInputMismatch(recognizer: Parser, e: RecognitionException): void { const msg = "mismatched input " + this.getTokenErrorDisplay(e.offendingToken) + - " expecting " + e.getExpectedTokens().toString(recognizer.vocabulary); + " expecting " + e.getExpectedTokens()!.toString(recognizer.vocabulary); recognizer.notifyErrorListeners(msg, e.offendingToken, e); } /** - * This is called by {@link //reportError} when the exception is a + * This is called by {@link reportError} when the exception is a * {@link FailedPredicateException}. * * @see //reportError @@ -292,8 +309,8 @@ export class DefaultErrorStrategy { * @param recognizer the parser instance * @param e the recognition exception */ - reportFailedPredicate(recognizer, e) { - const ruleName = recognizer.ruleNames[recognizer._ctx.ruleIndex]; + public reportFailedPredicate(recognizer: Parser, e: RecognitionException): void { + const ruleName = recognizer.ruleNames[recognizer.context!.ruleIndex]; const msg = "rule " + ruleName + " " + e.message; recognizer.notifyErrorListeners(msg, e.offendingToken, e); } @@ -305,19 +322,18 @@ export class DefaultErrorStrategy { * removed from the input stream. When this method returns, * {@code recognizer} is in error recovery mode. * - *

              This method is called when {@link //singleTokenDeletion} identifies + *

              This method is called when {@link singleTokenDeletion} identifies * single-token deletion as a viable recovery strategy for a mismatched * input error.

              * *

              The default implementation simply returns if the handler is already in - * error recovery mode. Otherwise, it calls {@link //beginErrorCondition} to + * error recovery mode. Otherwise, it calls {@link beginErrorCondition} to * enter error recovery mode, followed by calling * {@link Parser//notifyErrorListeners}.

              * * @param recognizer the parser instance - * */ - reportUnwantedToken(recognizer) { + public reportUnwantedToken(recognizer: Parser): void { if (this.inErrorRecoveryMode(recognizer)) { return; } @@ -335,18 +351,18 @@ export class DefaultErrorStrategy { * method is called, the missing token has not yet been inserted. When this * method returns, {@code recognizer} is in error recovery mode. * - *

              This method is called when {@link //singleTokenInsertion} identifies + *

              This method is called when {@link singleTokenInsertion} identifies * single-token insertion as a viable recovery strategy for a mismatched * input error.

              * *

              The default implementation simply returns if the handler is already in - * error recovery mode. Otherwise, it calls {@link //beginErrorCondition} to + * error recovery mode. Otherwise, it calls {@link beginErrorCondition} to * enter error recovery mode, followed by calling * {@link Parser//notifyErrorListeners}.

              * * @param recognizer the parser instance */ - reportMissingToken(recognizer) { + public reportMissingToken(recognizer: Parser): void { if (this.inErrorRecoveryMode(recognizer)) { return; } @@ -372,8 +388,7 @@ export class DefaultErrorStrategy { * token and delete it. Then consume and return the next token (which was * the {@code LA(2)} token) as the successful result of the match operation.

              * - *

              This recovery strategy is implemented by {@link - * //singleTokenDeletion}.

              + *

              This recovery strategy is implemented by {@link singleTokenDeletion}.

              * *

              MISSING TOKEN (single token insertion)

              * @@ -383,8 +398,7 @@ export class DefaultErrorStrategy { * "insertion" is performed by returning the created token as the successful * result of the match operation.

              * - *

              This recovery strategy is implemented by {@link - * //singleTokenInsertion}.

              + *

              This recovery strategy is implemented by {@link singleTokenInsertion}.

              * *

              EXAMPLE

              * @@ -405,17 +419,18 @@ export class DefaultErrorStrategy { * * * The attempt to match {@code ')'} will fail when it sees {@code ';'} and - * call {@link //recoverInline}. To recover, it sees that {@code LA(1)==';'} + * call {@link recoverInline}. To recover, it sees that {@code LA(1)==';'} * is in the set of tokens that can follow the {@code ')'} token reference * in rule {@code atom}. It can assume that you forgot the {@code ')'}. */ - recoverInline(recognizer) { + public recoverInline(recognizer: Parser): Token { // SINGLE TOKEN DELETION const matchedSymbol = this.singleTokenDeletion(recognizer); if (matchedSymbol !== null) { // we have deleted the extra token. // now, move past ttype token as if all were ok recognizer.consume(); + return matchedSymbol; } // SINGLE TOKEN INSERTION @@ -428,7 +443,7 @@ export class DefaultErrorStrategy { /** * This method implements the single-token insertion inline error recovery - * strategy. It is called by {@link //recoverInline} if the single-token + * strategy. It is called by {@link recoverInline} if the single-token * deletion strategy fails to recover from the mismatched input. If this * method returns {@code true}, {@code recognizer} will be in error recovery * mode. @@ -440,20 +455,21 @@ export class DefaultErrorStrategy { * token with the correct type to produce this behavior.

              * * @param recognizer the parser instance - * @return {@code true} if single-token insertion is a viable recovery + * @returns `true` if single-token insertion is a viable recovery * strategy for the current mismatched input, otherwise {@code false} */ - singleTokenInsertion(recognizer) { + public singleTokenInsertion(recognizer: Parser): boolean { const currentSymbolType = recognizer.tokenStream.LA(1); // if current token is consistent with what could come after current // ATN state, then we know we're missing a token; error recovery // is free to conjure up and insert the missing token - const atn = recognizer.interpreter.atn; + const atn = recognizer.atn; const currentState = atn.states[recognizer.state]; - const next = currentState.transitions[0].target; - const expectingAtLL2 = atn.nextTokens(next, recognizer._ctx); + const next = currentState!.transitions[0].target; + const expectingAtLL2 = atn.nextTokens(next, recognizer.context); if (expectingAtLL2.contains(currentSymbolType)) { this.reportMissingToken(recognizer); + return true; } else { return false; @@ -462,24 +478,24 @@ export class DefaultErrorStrategy { /** * This method implements the single-token deletion inline error recovery - * strategy. It is called by {@link //recoverInline} to attempt to recover + * strategy. It is called by {@link recoverInline} to attempt to recover * from mismatched input. If this method returns null, the parser and error * handler state will not have changed. If this method returns non-null, * {@code recognizer} will not be in error recovery mode since the * returned token was a successful match. * *

              If the single-token deletion is successful, this method calls - * {@link //reportUnwantedToken} to report the error, followed by + * {@link reportUnwantedToken} to report the error, followed by * {@link Parser//consume} to actually "delete" the extraneous token. Then, - * before returning {@link //reportMatch} is called to signal a successful + * before returning {@link reportMatch} is called to signal a successful * match.

              * * @param recognizer the parser instance - * @return the successfully matched {@link Token} instance if single-token + * @returns the successfully matched {@link Token} instance if single-token * deletion successfully recovers from the mismatched input, otherwise * {@code null} */ - singleTokenDeletion(recognizer) { + public singleTokenDeletion(recognizer: Parser): Token | null { const nextTokenType = recognizer.tokenStream.LA(2); const expecting = this.getExpectedTokens(recognizer); if (expecting.contains(nextTokenType)) { @@ -492,6 +508,7 @@ export class DefaultErrorStrategy { // we want to return the token we're actually matching const matchedSymbol = recognizer.getCurrentToken(); this.reportMatch(recognizer); // we know current token is correct + return matchedSymbol; } else { return null; @@ -519,8 +536,8 @@ export class DefaultErrorStrategy { * override this method to create the appropriate tokens. * */ - getMissingSymbol(recognizer) { - const currentSymbol = recognizer.getCurrentToken(); + public getMissingSymbol(recognizer: Parser): Token { + const currentSymbol = recognizer.getCurrentToken() as CommonToken; const expecting = this.getExpectedTokens(recognizer); let expectedTokenType = Token.INVALID_TYPE; if (!expecting.isNil) { @@ -535,7 +552,7 @@ export class DefaultErrorStrategy { } let current = currentSymbol; - const lookBack = recognizer.tokenStream.LT(-1); + const lookBack = recognizer.tokenStream.LT(-1) as CommonToken; if (current.type === Token.EOF && lookBack !== null) { current = lookBack; } @@ -545,7 +562,7 @@ export class DefaultErrorStrategy { -1, -1, current.line, current.column); } - getExpectedTokens(recognizer) { + public getExpectedTokens(recognizer: Parser): IntervalSet { return recognizer.getExpectedTokens(); } @@ -558,7 +575,7 @@ export class DefaultErrorStrategy { * your token objects because you don't have to go modify your lexer * so that it creates a new Java type. */ - getTokenErrorDisplay(t) { + public getTokenErrorDisplay(t: Token | null): string { if (t === null) { return ""; } @@ -570,13 +587,15 @@ export class DefaultErrorStrategy { s = "<" + t.type + ">"; } } + return this.escapeWSAndQuote(s); } - escapeWSAndQuote(s) { + public escapeWSAndQuote(s: string): string { s = s.replace(/\n/g, "\\n"); s = s.replace(/\r/g, "\\r"); s = s.replace(/\t/g, "\\t"); + return "'" + s + "'"; } @@ -673,24 +692,25 @@ export class DefaultErrorStrategy { * Like Grosch I implement context-sensitive FOLLOW sets that are combined * at run-time upon error to avoid overhead during parsing. */ - getErrorRecoverySet(recognizer) { - const atn = recognizer.interpreter.atn; - let ctx = recognizer._ctx; + public getErrorRecoverySet(recognizer: Parser): IntervalSet { + const atn = recognizer.atn; + let ctx: ParserRuleContext | null = recognizer.context; const recoverSet = new IntervalSet(); while (ctx !== null && ctx.invokingState >= 0) { // compute what follows who invoked us - const invokingState = atn.states[ctx.invokingState]; - const rt = invokingState.transitions[0]; + const invokingState = atn.states[ctx.invokingState]!; + const rt = invokingState.transitions[0] as RuleTransition; const follow = atn.nextTokens(rt.followState); recoverSet.addSet(follow); ctx = ctx.parent; } recoverSet.removeOne(Token.EPSILON); + return recoverSet; } // Consume tokens until one matches the given token set.// - consumeUntil(recognizer, set) { + public consumeUntil(recognizer: Parser, set: IntervalSet): void { let ttype = recognizer.tokenStream.LA(1); while (ttype !== Token.EOF && !set.contains(ttype)) { recognizer.consume(); diff --git a/src/DiagnosticErrorListener.d.ts b/src/DiagnosticErrorListener.d.ts deleted file mode 100644 index d0bd4b4..0000000 --- a/src/DiagnosticErrorListener.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BaseErrorListener } from "./BaseErrorListener.js"; -import { Recognizer } from "./Recognizer.js"; -import { RecognitionException } from "./RecognitionException.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { Token } from "./Token.js"; - -export declare class DiagnosticErrorListener - extends BaseErrorListener { - public syntaxError(recognizer: Recognizer, offendingSymbol: T, line: number, - column: number, msg: string, e: RecognitionException | null): void; -} diff --git a/src/DiagnosticErrorListener.js b/src/DiagnosticErrorListener.js deleted file mode 100644 index 34332cb..0000000 --- a/src/DiagnosticErrorListener.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BaseErrorListener } from './BaseErrorListener.js'; -import { Interval } from './misc/Interval.js'; -import { BitSet } from "./misc/BitSet.js"; - -/** - * This implementation of {@link ANTLRErrorListener} can be used to identify - * certain potential correctness and performance problems in grammars. "Reports" - * are made by calling {@link Parser//notifyErrorListeners} with the appropriate - * message. - * - *
                - *
              • Ambiguities: These are cases where more than one path through the - * grammar can match the input.
              • - *
              • Weak context sensitivity: These are cases where full-context - * prediction resolved an SLL conflict to a unique alternative which equaled the - * minimum alternative of the SLL conflict.
              • - *
              • Strong (forced) context sensitivity: These are cases where the - * full-context prediction resolved an SLL conflict to a unique alternative, - * and the minimum alternative of the SLL conflict was found to not be - * a truly viable alternative. Two-stage parsing cannot be used for inputs where - * this situation occurs.
              • - *
              - */ -export class DiagnosticErrorListener extends BaseErrorListener { - constructor(exactOnly) { - super(); - exactOnly = exactOnly || true; - // whether all ambiguities or only exact ambiguities are reported. - this.exactOnly = exactOnly; - } - - reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) { - if (this.exactOnly && !exact) { - return; - } - const msg = "reportAmbiguity d=" + - this.getDecisionDescription(recognizer, dfa) + - ": ambigAlts=" + - this.getConflictingAlts(ambigAlts, configs) + - ", input='" + - recognizer.tokenStream.getText(new Interval(startIndex, stopIndex)) + "'"; - recognizer.notifyErrorListeners(msg); - } - - reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) { - const msg = "reportAttemptingFullContext d=" + - this.getDecisionDescription(recognizer, dfa) + - ", input='" + - recognizer.tokenStream.getText(new Interval(startIndex, stopIndex)) + "'"; - recognizer.notifyErrorListeners(msg); - } - - reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) { - const msg = "reportContextSensitivity d=" + - this.getDecisionDescription(recognizer, dfa) + - ", input='" + - recognizer.tokenStream.getText(new Interval(startIndex, stopIndex)) + "'"; - recognizer.notifyErrorListeners(msg); - } - - getDecisionDescription(recognizer, dfa) { - const decision = dfa.decision; - const ruleIndex = dfa.atnStartState.ruleIndex; - - const ruleNames = recognizer.ruleNames; - if (ruleIndex < 0 || ruleIndex >= ruleNames.length) { - return "" + decision; - } - const ruleName = ruleNames[ruleIndex] || null; - if (ruleName === null || ruleName.length === 0) { - return "" + decision; - } - return `${decision} (${ruleName})`; - } - - /** - * Computes the set of conflicting or ambiguous alternatives from a - * configuration set, if that information was not already provided by the - * parser. - * - * @param reportedAlts The set of conflicting or ambiguous alternatives, as - * reported by the parser. - * @param configs The conflicting or ambiguous configuration set. - * @return Returns {@code reportedAlts} if it is not {@code null}, otherwise - * returns the set of alternatives represented in {@code configs}. - */ - getConflictingAlts(reportedAlts, configs) { - if (reportedAlts !== null) { - return reportedAlts; - } - const result = new BitSet(); - for (let i = 0; i < configs.items.length; i++) { - result.add(configs.items[i].alt); - } - return `{${result.values().join(", ")}}`; - } -} diff --git a/src/DiagnosticErrorListener.ts b/src/DiagnosticErrorListener.ts new file mode 100644 index 0000000..06b4b7d --- /dev/null +++ b/src/DiagnosticErrorListener.ts @@ -0,0 +1,143 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { BaseErrorListener } from "./BaseErrorListener.js"; +import { Parser } from "./Parser.js"; +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; +import { DFA } from "./dfa/DFA.js"; +import { BitSet } from "./misc/BitSet.js"; +import { Interval } from "./misc/Interval.js"; + +/** + * This implementation of {@link ANTLRErrorListener} can be used to identify + * certain potential correctness and performance problems in grammars. "Reports" + * are made by calling {@link Parser#notifyErrorListeners} with the appropriate + * message. + * + *
                + *
              • Ambiguities: These are cases where more than one path through the + * grammar can match the input.
              • + *
              • Weak context sensitivity: These are cases where full-context + * prediction resolved an SLL conflict to a unique alternative which equaled the + * minimum alternative of the SLL conflict.
              • + *
              • Strong (forced) context sensitivity: These are cases where the + * full-context prediction resolved an SLL conflict to a unique alternative, + * and the minimum alternative of the SLL conflict was found to not be + * a truly viable alternative. Two-stage parsing cannot be used for inputs where + * this situation occurs.
              • + *
              + * + * @author Sam Harwell + */ +export class DiagnosticErrorListener extends BaseErrorListener { + /** + * When `true`, only exactly known ambiguities are reported. + */ + protected readonly exactOnly: boolean; + + /** + * Initializes a new instance of {@link DiagnosticErrorListener} which only + * reports exact ambiguities. + */ + public constructor(); + /** + * Initializes a new instance of {@link DiagnosticErrorListener}, specifying + * whether all ambiguities or only exact ambiguities are reported. + * + * @param exactOnly `true` to report only exact ambiguities, otherwise + * {@code false} to report all ambiguities. + */ + public constructor(exactOnly: boolean); + public constructor(exactOnly?: boolean) { + super(); + this.exactOnly = exactOnly ?? true; + } + + public override reportAmbiguity = (recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + exact: boolean, + ambigAlts: BitSet | null, + configs: ATNConfigSet): void => { + if (this.exactOnly && !exact) { + return; + } + + const decision = this.getDecisionDescription(recognizer, dfa); + const conflictingAlts = this.getConflictingAlts(ambigAlts, configs); + const text = recognizer.tokenStream.getText(Interval.of(startIndex, stopIndex)); + const message = `reportAmbiguity d=${decision}: ambigAlts=${conflictingAlts}, input='${text}'`; + recognizer.notifyErrorListeners(message, null, null); + }; + + public override reportAttemptingFullContext = (recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + _conflictingAlts: BitSet | null, + _configs: ATNConfigSet | null): void => { + const decision = this.getDecisionDescription(recognizer, dfa); + const text = recognizer.tokenStream.getText(Interval.of(startIndex, stopIndex)); + const message = `reportAttemptingFullContext d=${decision}, input='${text}'`; + recognizer.notifyErrorListeners(message, null, null); + }; + + public override reportContextSensitivity = (recognizer: Parser, + dfa: DFA, + startIndex: number, + stopIndex: number, + _prediction: number, + _configs: ATNConfigSet | null): void => { + const decision = this.getDecisionDescription(recognizer, dfa); + const text = recognizer.tokenStream.getText(Interval.of(startIndex, stopIndex)); + const message = `reportContextSensitivity d=${decision}, input='${text}'`; + recognizer.notifyErrorListeners(message, null, null); + }; + + protected getDecisionDescription = (recognizer: Parser, dfa: DFA): string => { + const decision = dfa.decision; + const ruleIndex = dfa.atnStartState!.ruleIndex; + + const ruleNames = recognizer.ruleNames; + if (ruleIndex < 0 || ruleIndex >= ruleNames.length) { + return decision.toString(); + } + + const ruleName = ruleNames[ruleIndex]; + if (ruleName.length === 0) { + return decision.toString(); + } + + return `${decision} (${ruleName})`; + }; + + /** + * Computes the set of conflicting or ambiguous alternatives from a + * configuration set, if that information was not already provided by the + * parser. + * + * @param reportedAlts The set of conflicting or ambiguous alternatives, as + * reported by the parser. + * @param configs The conflicting or ambiguous configuration set. + * @returns Returns {@code reportedAlts} if it is not {@code null}, otherwise + * returns the set of alternatives represented in {@code configs}. + */ + protected getConflictingAlts = (reportedAlts: BitSet | null, + configs: ATNConfigSet): BitSet | null => { + if (reportedAlts !== null) { + return reportedAlts; + } + + const result = new BitSet(); + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < configs.configs.length; i++) { + result.set(configs.configs[i].alt); + } + + return result; + }; +}; diff --git a/src/FailedPredicateException.d.ts b/src/FailedPredicateException.d.ts deleted file mode 100644 index 6afa5e4..0000000 --- a/src/FailedPredicateException.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { RecognitionException } from "./RecognitionException.js"; -import { Parser } from "./Parser.js"; - -export declare class FailedPredicateException extends RecognitionException { - public constructor(recognizer: Parser, predicate: string | undefined, message: string | undefined); -} diff --git a/src/FailedPredicateException.js b/src/FailedPredicateException.ts similarity index 67% rename from src/FailedPredicateException.js rename to src/FailedPredicateException.ts index 405326c..a259f45 100644 --- a/src/FailedPredicateException.js +++ b/src/FailedPredicateException.ts @@ -5,6 +5,7 @@ */ import { PredicateTransition } from "./atn/PredicateTransition.js"; +import { Parser } from "./Parser.js"; import { RecognitionException } from "./RecognitionException.js"; /** @@ -14,14 +15,17 @@ import { RecognitionException } from "./RecognitionException.js"; * prediction. */ export class FailedPredicateException extends RecognitionException { + private readonly ruleIndex: number = 0; + private readonly predicateIndex: number = 0; + private readonly predicate?: string; - constructor(recognizer, predicate, message) { + public constructor(recognizer: Parser, predicate?: string, message: string | null = null) { super({ - message: formatMessage(predicate, message || null), - recognizer: recognizer, - input: recognizer.inputStream, ctx: recognizer._ctx + message: formatMessage(predicate ?? "no predicate", message ?? null), + recognizer, + input: recognizer.inputStream, ctx: recognizer.context, }); - const s = recognizer.interpreter.atn.states[recognizer.state]; + const s = recognizer.atn.states[recognizer.state]!; const trans = s.transitions[0]; if (trans instanceof PredicateTransition) { this.ruleIndex = trans.ruleIndex; @@ -35,11 +39,10 @@ export class FailedPredicateException extends RecognitionException { } } - -function formatMessage(predicate, message) { +const formatMessage = (predicate: string | null, message: string | null) => { if (message !== null) { return message; } else { return "failed predicate: {" + predicate + "}?"; } -} +}; diff --git a/src/InputMismatchException.d.ts b/src/InputMismatchException.d.ts deleted file mode 100644 index af4c766..0000000 --- a/src/InputMismatchException.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { RecognitionException } from "./RecognitionException.js"; -import { Parser } from "./Parser.js"; - -export declare class InputMismatchException extends RecognitionException { - public constructor(recognizer: Parser); -} diff --git a/src/InputMismatchException.js b/src/InputMismatchException.ts similarity index 74% rename from src/InputMismatchException.js rename to src/InputMismatchException.ts index 7478652..0aa12a4 100644 --- a/src/InputMismatchException.js +++ b/src/InputMismatchException.ts @@ -5,14 +5,15 @@ */ import { RecognitionException } from "./RecognitionException.js"; +import { Parser } from "./Parser.js"; /** * This signifies any kind of mismatched input exceptions such as * when the current input does not match the expected token. */ export class InputMismatchException extends RecognitionException { - constructor(recognizer) { - super({ message: "", recognizer: recognizer, input: recognizer.inputStream, ctx: recognizer._ctx }); + public constructor(recognizer: Parser) { + super({ message: "", recognizer, input: recognizer.inputStream, ctx: recognizer.context }); this.offendingToken = recognizer.getCurrentToken(); } } diff --git a/src/InputStream.d.ts b/src/InputStream.d.ts deleted file mode 100644 index 578e54c..0000000 --- a/src/InputStream.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from "./CharStream.js"; -import { CharStreams } from "./CharStreams.js"; -import { Interval } from "./misc/Interval.js"; - -/** - * @deprecated Use {@link CharStreams} instead. - */ -export declare class InputStream implements CharStream { - public constructor(data: string); - public constructor(data: string, decodeToUnicodeCodePoints: boolean); - - // XXX: the following methods are not implemented currently! - public getText(interval: Interval): string; - public getText(start: number, stop: number): string; - - public reset(): void; - public toString(): string; - - public consume(): void; - - // eslint-disable-next-line @typescript-eslint/naming-convention - public LA(i: number): number; - - public mark(): number; - - public release(marker: number): void; - - public get index(): number; - - public seek(index: number): void; - - public get size(): number; - - public getSourceName(): string; -} diff --git a/src/InputStream.js b/src/InputStream.js deleted file mode 100644 index 057ab70..0000000 --- a/src/InputStream.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from './CharStream.js'; - -/** - * @deprecated Use CharStream instead -*/ -export class InputStream extends CharStream { - constructor(data, decodeToUnicodeCodePoints) { - super(data, decodeToUnicodeCodePoints); - } -} diff --git a/src/IntStream.js b/src/IntStream.js deleted file mode 100644 index 96a1e1d..0000000 --- a/src/IntStream.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class IntStream { - EOF = -1; - UNKNOWN_SOURCE_NAME = ""; -} diff --git a/src/IntStream.d.ts b/src/IntStream.ts similarity index 98% rename from src/IntStream.d.ts rename to src/IntStream.ts index 9d5d815..ae5b2d7 100644 --- a/src/IntStream.d.ts +++ b/src/IntStream.ts @@ -4,8 +4,6 @@ * can be found in the LICENSE.txt file in the project root. */ -import type { CommonTokenStream } from "./CommonTokenStream.js"; - /** * A simple stream of symbols whose values are represented as integers. This * interface provides marked ranges with support for a minimum level @@ -22,7 +20,7 @@ import type { CommonTokenStream } from "./CommonTokenStream.js"; *
            • {@link #size}
            • *
            */ -export declare interface IntStream { +export interface IntStream { /** * Consumes the current symbol in the stream. This method has the following * effects: @@ -203,7 +201,8 @@ export declare interface IntStream { getSourceName(): string; } -export declare namespace IntStream { +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace IntStream { /** * The value returned by {@link IntStream.LA} when the end of the stream is * reached. diff --git a/src/InterpreterRuleContext.js b/src/InterpreterRuleContext.js deleted file mode 100644 index ac0b426..0000000 --- a/src/InterpreterRuleContext.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParserRuleContext } from "./ParserRuleContext.js"; - -export class InterpreterRuleContext extends ParserRuleContext { - _ruleIndex; - - constructor(ruleIndex, parent, invokingStateNumber) { - if (invokingStateNumber !== undefined) { - super(parent, invokingStateNumber); - } else { - super(); - } - - this._ruleIndex = ruleIndex; - } - - get ruleIndex() { - return this._ruleIndex; - } -} diff --git a/src/InterpreterRuleContext.d.ts b/src/InterpreterRuleContext.ts similarity index 66% rename from src/InterpreterRuleContext.d.ts rename to src/InterpreterRuleContext.ts index d17e074..5b5020b 100644 --- a/src/InterpreterRuleContext.d.ts +++ b/src/InterpreterRuleContext.ts @@ -10,24 +10,29 @@ import { ParserRuleContext } from "./ParserRuleContext.js"; * This class extends {@link ParserRuleContext} by allowing the value of * {@link #getRuleIndex} to be explicitly set for the context. * + *

            * {@link ParserRuleContext} does not include field storage for the rule index * since the context classes created by the code generator override the * {@link #getRuleIndex} method to return the correct value for that context. * Since the parser interpreter does not use the context classes generated for a * parser, this class (with slightly more memory overhead per node) is used to - * provide equivalent functionality. + * provide equivalent functionality.

            */ export class InterpreterRuleContext extends ParserRuleContext { - public constructor(ruleIndex: number); - /** - * Constructs a new {@link InterpreterRuleContext} with the specified - * parent, invoking state, and rule index. - * - * @param ruleIndex The rule index for the current context. - * @param parent The parent context. - * @param invokingStateNumber The invoking state number. - */ - public constructor(ruleIndex: number, parent: ParserRuleContext | undefined, invokingStateNumber: number); + /** This is the backing field for {@link #getRuleIndex}. */ + #ruleIndex: number; - public get ruleIndex(): number; + public constructor(ruleIndex: number, parent: ParserRuleContext | null, invokingStateNumber?: number) { + if (invokingStateNumber !== undefined) { + super(parent, invokingStateNumber); + } else { + super(); + } + + this.#ruleIndex = ruleIndex; + } + + public override get ruleIndex(): number { + return this.#ruleIndex; + } } diff --git a/src/Lexer.d.ts b/src/Lexer.d.ts deleted file mode 100644 index 09477e7..0000000 --- a/src/Lexer.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Recognizer } from "./Recognizer.js"; -import { LexerATNSimulator } from "./atn/LexerATNSimulator.js"; -import { CharStream } from "./CharStream.js"; -import { Token } from "./Token.js"; -import { TokenSource } from "./TokenSource.js"; -import { TokenFactory } from "./TokenFactory.js"; -import { InputStream } from "./InputStream.js"; - -export declare abstract class Lexer extends Recognizer implements TokenSource { - /* eslint-disable @typescript-eslint/naming-convention */ - public static DEFAULT_MODE: number; - public static MORE: number; - public static SKIP: number; - - public static DEFAULT_TOKEN_CHANNEL: number; - public static MIN_CHAR_VALUE: number; - public static MAX_CHAR_VALUE: number; - - public static HIDDEN: number; - - public _factory: TokenFactory; - public _tokenFactorySourcePair: [TokenSource | null, InputStream | null]; - /* eslint-enable @typescript-eslint/naming-convention */ - - public text: string; - public line: number; - public column: number; - - public _channel: number; - public _token: Token | null; - public _tokenStartCharIndex: number; - public _tokenStartCharPositionInLine: number; - public _tokenStartLine: number; - public _tokenStartColumn: number; - public _type: number; - - public get inputStream(): CharStream; - public set inputStream(value: CharStream); - - public get sourceName(): string; - - protected _modeStack: number[]; - - public constructor(input: CharStream); - - public getErrorDisplay(s: string | number): string; - - public reset(): void; - public nextToken(): Token; - public skip(): void; - public more(): void; - public more(m: number): void; - public pushMode(m: number): void; - public popMode(): number; - public emitToken(token: Token): void; - public emit(): Token; - public emitEOF(): Token; - public getAllTokens(): Token[]; -} diff --git a/src/Lexer.js b/src/Lexer.ts similarity index 60% rename from src/Lexer.js rename to src/Lexer.ts index 9d90bef..0c5b492 100644 --- a/src/Lexer.js +++ b/src/Lexer.ts @@ -4,73 +4,92 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from './Token.js'; -import { Recognizer } from './Recognizer.js'; -import { CommonTokenFactory } from './CommonTokenFactory.js'; -import { RecognitionException } from './RecognitionException.js'; -import { LexerNoViableAltException } from './LexerNoViableAltException.js'; +/* eslint-disable jsdoc/require-returns, jsdoc/require-param, @typescript-eslint/naming-convention */ +/* eslint-disable no-underscore-dangle */ + +import { Token } from "./Token.js"; +import { Recognizer } from "./Recognizer.js"; +import { CommonTokenFactory } from "./CommonTokenFactory.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { LexerNoViableAltException } from "./LexerNoViableAltException.js"; +import { LexerATNSimulator } from "./atn/LexerATNSimulator.js"; +import { CharStream } from "./CharStream.js"; +import { TokenFactory } from "./TokenFactory.js"; +import { TokenSource } from "./TokenSource.js"; /** * A lexer is recognizer that draws input symbols from a character stream. * lexer grammars result in a subclass of this object. A Lexer object * uses simplified match() and error recovery mechanisms in the interest of speed. */ -export class Lexer extends Recognizer { - constructor(input) { - super(); - this._input = input; - this._factory = CommonTokenFactory.DEFAULT; - this._tokenFactorySourcePair = [this, input]; +export abstract class Lexer extends Recognizer implements TokenSource { + public static DEFAULT_MODE = 0; + public static MORE = -2; + public static SKIP = -3; - this.interpreter = null; // child classes must populate this + public static DEFAULT_TOKEN_CHANNEL = Token.DEFAULT_CHANNEL; + public static HIDDEN = Token.HIDDEN_CHANNEL; + public static MIN_CHAR_VALUE = 0x0000; + public static MAX_CHAR_VALUE = 0x10FFFF; - /** - * The goal of all lexer rules/methods is to create a token object. - * this is an instance variable as multiple rules may collaborate to - * create a single token. nextToken will return this object after - * matching lexer rule(s). If you subclass to allow multiple token - * emissions, then set this to the last token to be matched or - * something non-null so that the auto token emit mechanism will not - * emit another token. - */ - this._token = null; + public _input: CharStream; - /** - * What character index in the stream did the current token start at? - * Needed, for example, to get the text for current token. Set at - * the start of nextToken. - */ - this._tokenStartCharIndex = -1; + /** + * The goal of all lexer rules/methods is to create a token object. + * This is an instance variable as multiple rules may collaborate to + * create a single token. nextToken will return this object after + * matching lexer rule(s). If you subclass to allow multiple token + * emissions, then set this to the last token to be matched or + * something nonnull so that the auto token emit mechanism will not + * emit another token. + */ + public _token: Token | null = null; - // The line on which the first character of the token resides/// - this._tokenStartLine = -1; + /** + * What character index in the stream did the current token start at? + * Needed, for example, to get the text for current token. Set at + * the start of nextToken. + */ + public _tokenStartCharIndex = -1; - // The character position of first character within the line/// - this._tokenStartColumn = -1; + /** The line on which the first character of the token resides */ + public _tokenStartLine = 0; - // Once we see EOF on char stream, next token will be EOF. - // If you have DONE : EOF ; then you see DONE EOF. - this._hitEOF = false; + /** The character position of first character within the line */ + public _tokenStartColumn = 0; - // The channel number for the current token/// - this._channel = Token.DEFAULT_CHANNEL; + /** + * Once we see EOF on char stream, next token will be EOF. + * If you have DONE : EOF ; then you see DONE EOF. + */ + public _hitEOF = false; - // The token type for the current token/// - this._type = Token.INVALID_TYPE; + /** The channel number for the current token */ + public _channel = 0; - this._modeStack = []; - this._mode = Lexer.DEFAULT_MODE; + /** The token type for the current token */ + public _type = 0; - /** - * You can set the text for the current token to override what is in - * the input char buffer. Use setText() or can set this instance var. - */ - this._text = null; + public _modeStack: number[] = []; + public _mode: number = Lexer.DEFAULT_MODE; + + /** + * You can set the text for the current token to override what is in + * the input char buffer. Use setText() or can set this instance var. + */ + public _text: string | null = null; + + protected _factory: TokenFactory; + + public constructor(input: CharStream) { + super(); + this._input = input; + this._factory = CommonTokenFactory.DEFAULT; } - reset() { + public reset(seekBack = true): void { // wack Lexer state variables - if (this._input !== null) { + if (seekBack) { this._input.seek(0); // rewind the input } this._token = null; @@ -89,9 +108,9 @@ export class Lexer extends Recognizer { } // Return a token from this source; i.e., match a token on the char stream. - nextToken() { + public nextToken(): Token { if (this._input === null) { - throw "nextToken requires a non-null input stream."; + throw new Error("nextToken requires a non-null input stream."); } /** @@ -103,7 +122,8 @@ export class Lexer extends Recognizer { for (; ;) { if (this._hitEOF) { this.emitEOF(); - return this._token; + + return this._token!; } this._token = null; this._channel = Token.DEFAULT_CHANNEL; @@ -118,11 +138,10 @@ export class Lexer extends Recognizer { try { ttype = this.interpreter.match(this._input, this._mode); } catch (e) { - if (e instanceof RecognitionException) { + if (e instanceof LexerNoViableAltException) { this.notifyListeners(e); // report error this.recover(e); } else { - console.log(e.stack); throw e; } } @@ -146,7 +165,8 @@ export class Lexer extends Recognizer { if (this._token === null) { this.emit(); } - return this._token; + + return this._token!; } } finally { // make sure we release marker after match or @@ -162,34 +182,35 @@ export class Lexer extends Recognizer { * if token==null at end of any token rule, it creates one for you * and emits it. */ - skip() { + public skip(): void { this._type = Lexer.SKIP; } - more() { + public more(): void { this._type = Lexer.MORE; } - mode(m) { + public mode(m: number): void { this._mode = m; } - pushMode(m) { - if (this.interpreter.debug) { + public pushMode(m: number): void { + if (LexerATNSimulator.debug) { console.log("pushMode " + m); } this._modeStack.push(this._mode); this.mode(m); } - popMode() { + public popMode(): number { if (this._modeStack.length === 0) { - throw "Empty Stack"; + throw new Error("Empty Stack"); } - if (this.interpreter.debug) { + if (LexerATNSimulator.debug) { console.log("popMode back to " + this._modeStack.slice(0, -1)); } - this.mode(this._modeStack.pop()); + this.mode(this._modeStack.pop()!); + return this._mode; } @@ -199,7 +220,7 @@ export class Lexer extends Recognizer { * and getToken (to push tokens into a list and pull from that list * rather than a single variable as this implementation does). */ - emitToken(token) { + public emitToken(token: Token): void { this._token = token; } @@ -210,27 +231,29 @@ export class Lexer extends Recognizer { * use that to set the token's text. Override this method to emit * custom Token objects or provide a new factory. */ - emit() { - const t = this._factory.create(this._tokenFactorySourcePair, this._type, + public emit(): Token { + const t = this._factory.create([this, this._input], this._type, this._text, this._channel, this._tokenStartCharIndex, this .getCharIndex() - 1, this._tokenStartLine, this._tokenStartColumn); this.emitToken(t); + return t; } - emitEOF() { + public emitEOF(): Token { const cpos = this.column; const lpos = this.line; - const eof = this._factory.create(this._tokenFactorySourcePair, Token.EOF, + const eof = this._factory.create([this, this._input], Token.EOF, null, Token.DEFAULT_CHANNEL, this._input.index, this._input.index - 1, lpos, cpos); this.emitToken(eof); + return eof; } // What is the index of the current character of lookahead?/// - getCharIndex() { + public getCharIndex(): number { return this._input.index; } @@ -238,17 +261,18 @@ export class Lexer extends Recognizer { * Return a list of all Token objects in input char stream. * Forces load of all tokens. Does not include EOF token. */ - getAllTokens() { + public getAllTokens(): Token[] { const tokens = []; - let t = this.nextToken(); + let t = this.nextToken()!; while (t.type !== Token.EOF) { tokens.push(t); - t = this.nextToken(); + t = this.nextToken()!; } + return tokens; } - notifyListeners(e) { + public notifyListeners(e: LexerNoViableAltException): void { const start = this._tokenStartCharIndex; const stop = this._input.index; const text = this._input.getText(start, stop); @@ -258,29 +282,25 @@ export class Lexer extends Recognizer { this._tokenStartColumn, msg, e); } - getErrorDisplay(s) { - const d = []; - for (let i = 0; i < s.length; i++) { - d.push(s[i]); - } - return d.join(''); + public getErrorDisplay(s: string): string { + return s; } - getErrorDisplayForChar(c) { + public getErrorDisplayForChar(c: string): string { if (c.charCodeAt(0) === Token.EOF) { return ""; - } else if (c === '\n') { + } else if (c === "\n") { return "\\n"; - } else if (c === '\t') { + } else if (c === "\t") { return "\\t"; - } else if (c === '\r') { + } else if (c === "\r") { return "\\r"; } else { return c; } } - getCharErrorDisplay(c) { + public getCharErrorDisplay(c: string): string { return "'" + this.getErrorDisplayForChar(c) + "'"; } @@ -290,7 +310,7 @@ export class Lexer extends Recognizer { * it all works out. You can instead use the rule invocation stack * to do sophisticated error recovery if you are in a fragment rule. */ - recover(re) { + public recover(re: LexerNoViableAltException | RecognitionException): void { if (this._input.LA(1) !== Token.EOF) { if (re instanceof LexerNoViableAltException) { // skip a char and try again @@ -302,47 +322,52 @@ export class Lexer extends Recognizer { } } - get inputStream() { + public get inputStream(): CharStream { return this._input; } - set inputStream(input) { - this._input = null; - this._tokenFactorySourcePair = [this, null]; - this.reset(); + public set inputStream(input: CharStream) { + this.reset(false); this._input = input; - this._tokenFactorySourcePair = [this, input]; } - get sourceName() { - return this._input.sourceName; + public set tokenFactory(factory: TokenFactory) { + this._factory = factory; + }; + + public get tokenFactory(): TokenFactory { + return this._factory; + }; + + public get sourceName(): string { + return this._input.getSourceName(); } - get type() { + public get type(): number { return this._type; } - set type(type) { + public set type(type: number) { this._type = type; } - get line() { + public get line(): number { return this.interpreter.line; } - set line(line) { + public set line(line: number) { this.interpreter.line = line; } - get column() { + public get column(): number { return this.interpreter.column; } - set column(column) { + public set column(column: number) { this.interpreter.column = column; } - get text() { + public get text(): string { if (this._text !== null) { return this._text; } else { @@ -350,16 +375,7 @@ export class Lexer extends Recognizer { } } - set text(text) { + public set text(text: string | null) { this._text = text; } } - -Lexer.DEFAULT_MODE = 0; -Lexer.MORE = -2; -Lexer.SKIP = -3; - -Lexer.DEFAULT_TOKEN_CHANNEL = Token.DEFAULT_CHANNEL; -Lexer.HIDDEN = Token.HIDDEN_CHANNEL; -Lexer.MIN_CHAR_VALUE = 0x0000; -Lexer.MAX_CHAR_VALUE = 0x10FFFF; diff --git a/src/LexerInterpreter.d.ts b/src/LexerInterpreter.d.ts deleted file mode 100644 index afc3a9b..0000000 --- a/src/LexerInterpreter.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATN } from "./atn/ATN.js"; -import { RuleContext } from "./atn/RuleContext.js"; -import { CharStream } from "./CharStream.js"; -import { Lexer } from "./Lexer.js"; -import { Vocabulary } from "./Vocabulary.js"; - -export declare abstract class LexerInterpreter extends Lexer { - - public constructor(grammarFileName: string, vocabulary: Vocabulary, ruleNames: string[], channelNames: string[], - modeNames: string[], atn: ATN, input: CharStream); - - public get atn(): ATN; - public get grammarFileName(): string; - public get ruleNames(): string[]; - public get modeNames(): string[]; - public get vocabulary(): Vocabulary; - - public abstract action(localctx: RuleContext | null, ruleIndex: number, actionIndex: number): void; -} diff --git a/src/LexerInterpreter.js b/src/LexerInterpreter.ts similarity index 63% rename from src/LexerInterpreter.js rename to src/LexerInterpreter.ts index 8ef988b..28a2398 100644 --- a/src/LexerInterpreter.js +++ b/src/LexerInterpreter.ts @@ -9,22 +9,25 @@ import { Lexer } from "./Lexer.js"; import { LexerATNSimulator } from "./atn/LexerATNSimulator.js"; import { PredictionContextCache } from "./atn/PredictionContextCache.js"; import { DFA } from "./dfa/DFA.js"; +import { ATN } from "./atn/ATN.js"; +import { Vocabulary } from "./Vocabulary.js"; +import { CharStream } from "./CharStream.js"; export class LexerInterpreter extends Lexer { - #grammarFileName; - #atn; + #grammarFileName: string; + #atn: ATN; - #ruleNames; - #channelNames; - #modeNames; + #ruleNames: string[]; + #channelNames: string[]; + #modeNames: string[]; - #vocabulary; - #decisionToDFA; + #vocabulary: Vocabulary; + #decisionToDFA: DFA[]; #sharedContextCache = new PredictionContextCache(); - constructor(grammarFileName, vocabulary, ruleNames, channelNames, modeNames, atn, - input) { + public constructor(grammarFileName: string, vocabulary: Vocabulary, ruleNames: string[], channelNames: string[], + modeNames: string[], atn: ATN, input: CharStream) { super(input); if (atn.grammarType !== ATNType.LEXER) { @@ -39,34 +42,34 @@ export class LexerInterpreter extends Lexer { this.#modeNames = modeNames.slice(0); this.#vocabulary = vocabulary; - this.#decisionToDFA = atn.decisionToState.map(function (ds, i) { + this.#decisionToDFA = atn.decisionToState.map((ds, i) => { return new DFA(ds, i); }); this.interpreter = new LexerATNSimulator(this, atn, this.#decisionToDFA, this.#sharedContextCache); } - get atn() { + public override get atn(): ATN { return this.#atn; } - get grammarFileName() { + public get grammarFileName(): string { return this.#grammarFileName; } - get ruleNames() { + public get ruleNames(): string[] { return this.#ruleNames; } - get channelNames() { + public get channelNames(): string[] { return this.#channelNames; } - get modeNames() { + public get modeNames(): string[] { return this.#modeNames; } - get vocabulary() { + public get vocabulary(): Vocabulary { return this.#vocabulary; } } diff --git a/src/LexerNoViableAltException.js b/src/LexerNoViableAltException.js deleted file mode 100644 index 3b7d144..0000000 --- a/src/LexerNoViableAltException.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Interval } from "./misc/Interval.js"; -import { RecognitionException } from "./RecognitionException.js"; - -export class LexerNoViableAltException extends RecognitionException { - constructor(lexer, input, startIndex, deadEndConfigs) { - super({ message: "", recognizer: lexer, input: input, ctx: null }); - this.startIndex = startIndex; - this.deadEndConfigs = deadEndConfigs; - } - - toString() { - let symbol = ""; - if (this.startIndex >= 0 && this.startIndex < this.input.size) { - symbol = this.input.getText(new Interval(this.startIndex, this.startIndex)); - } - return "LexerNoViableAltException" + symbol; - } -} diff --git a/src/LexerNoViableAltException.d.ts b/src/LexerNoViableAltException.ts similarity index 51% rename from src/LexerNoViableAltException.d.ts rename to src/LexerNoViableAltException.ts index 57fb534..7c51dda 100644 --- a/src/LexerNoViableAltException.d.ts +++ b/src/LexerNoViableAltException.ts @@ -4,18 +4,29 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; import { CharStream } from "./CharStream.js"; import { Lexer } from "./Lexer.js"; -import { TokenStream } from "./TokenStream.js"; -import { ATNConfigSet } from "./atn/ATNConfigSet.js"; import { RecognitionException } from "./RecognitionException.js"; +import { TokenStream } from "./TokenStream.js"; -export declare class LexerNoViableAltException extends RecognitionException { +export class LexerNoViableAltException extends RecognitionException { public startIndex: number; public deadEndConfigs: ATNConfigSet; public constructor(lexer: Lexer, input: CharStream | TokenStream, startIndex: number, - deadEndConfigs: ATNConfigSet); + deadEndConfigs: ATNConfigSet) { + super({ message: "", recognizer: lexer, input, ctx: null }); + this.startIndex = startIndex; + this.deadEndConfigs = deadEndConfigs; + } + + public override toString(): string { + let symbol = ""; + if (this.input && this.startIndex >= 0 && this.startIndex < this.input.size) { + symbol = this.input.getText(this.startIndex, this.startIndex); + } - public toString(): string; + return "LexerNoViableAltException" + symbol; + } } diff --git a/src/NoViableAltException.d.ts b/src/NoViableAltException.d.ts deleted file mode 100644 index 9bc9859..0000000 --- a/src/NoViableAltException.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNConfigSet } from "./atn/ATNConfigSet.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { Recognizer } from "./Recognizer.js"; -import { Token } from "./Token.js"; -import { RecognitionException } from "./RecognitionException.js"; - -export declare class NoViableAltException extends RecognitionException { - public deadEndConfigs: ATNConfigSet; - public startToken: Token; - - public constructor(recognizer: Recognizer); -} diff --git a/src/NoViableAltException.js b/src/NoViableAltException.ts similarity index 55% rename from src/NoViableAltException.js rename to src/NoViableAltException.ts index 9d410bc..820e30e 100644 --- a/src/NoViableAltException.js +++ b/src/NoViableAltException.ts @@ -4,7 +4,12 @@ * can be found in the LICENSE.txt file in the project root. */ +import { Parser } from "./Parser.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; import { RecognitionException } from "./RecognitionException.js"; +import { Token } from "./Token.js"; +import { TokenStream } from "./TokenStream.js"; +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; /** * Indicates that the parser could not decide which of two or more paths @@ -13,13 +18,28 @@ import { RecognitionException } from "./RecognitionException.js"; * in the various paths when the error. Reported by reportNoViableAlternative() */ export class NoViableAltException extends RecognitionException { - constructor(recognizer, input, startToken, offendingToken, deadEndConfigs, ctx) { - ctx = ctx ?? recognizer._ctx; + /** Which configurations did we try at input.index() that couldn't match input.LT(1)? */ + + public readonly deadEndConfigs: ATNConfigSet | null = null; + + /** + * The token object at the start index; the input stream might + * not be buffering tokens so get a reference to it. (At the + * time the error occurred, of course the stream needs to keep a + * buffer all of the tokens but later we might not have access to those.) + */ + + public readonly startToken: Token | null; + + public constructor(recognizer: Parser, input: TokenStream | null = null, startToken: Token | null = null, + offendingToken: Token | null = null, deadEndConfigs: ATNConfigSet | null = null, + ctx: ParserRuleContext | null = null) { + ctx = ctx ?? recognizer.context; offendingToken = offendingToken ?? recognizer.getCurrentToken(); startToken = startToken ?? recognizer.getCurrentToken(); input = input ?? recognizer.inputStream; - super({ message: "", recognizer: recognizer, input: input, ctx: ctx }); + super({ message: "", recognizer, input, ctx }); // Which configurations did we try at input.index() that couldn't match // input.LT(1)?// diff --git a/src/ParseCancellationException.d.ts b/src/ParseCancellationException.d.ts deleted file mode 100644 index 1256581..0000000 --- a/src/ParseCancellationException.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class ParseCancellationException extends Error { - public constructor(message: string); -} diff --git a/src/ParseCancellationException.js b/src/ParseCancellationException.js deleted file mode 100644 index dc123f4..0000000 --- a/src/ParseCancellationException.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class ParseCancellationException extends Error { - constructor() { - super(); - Error.captureStackTrace(this, ParseCancellationException); - } -} diff --git a/src/Parser.d.ts b/src/Parser.d.ts deleted file mode 100644 index 9704592..0000000 --- a/src/Parser.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Recognizer } from "./Recognizer.js"; -import { Token } from "./Token.js"; -import { TokenFactory } from "./TokenFactory.js"; -import { TokenStream } from "./TokenStream.js"; -import { ParserATNSimulator } from "./atn/ParserATNSimulator.js"; -import { ParserRuleContext } from "./ParserRuleContext.js"; -import { ANTLRErrorStrategy } from "./ANTLRErrorStrategy.js"; -import { RecognitionException } from "./RecognitionException.js"; -import { IntervalSet } from "./misc/IntervalSet.js"; -import { ParseTreeListener } from "./tree/ParseTreeListener.js"; -import { TerminalNode } from "./tree/TerminalNode.js"; -import { ErrorNode } from "./tree/ErrorNode.js"; - -export declare abstract class Parser extends Recognizer { - public errorHandler: ANTLRErrorStrategy; - public matchedEOF: boolean; - public buildParseTrees: boolean; - - protected _ctx: ParserRuleContext; - protected _parseListeners: ParseTreeListener[] | null; - - public constructor(input: TokenStream); - - public get context(): ParserRuleContext; - - public reset(): void; - public match(ttype: number): Token; - public matchWildcard(): Token; - public getParseListeners(): ParseTreeListener[]; - public addParseListener(listener: ParseTreeListener): void; - public removeParseListener(listener: ParseTreeListener): void; - public removeParseListeners(): void; - public triggerEnterRuleEvent(): void; - public triggerExitRuleEvent(): void; - - public getTokenFactory(): TokenFactory; - public setTokenFactory(factory: TokenFactory): void; - - public getATNWithBypassAlts(): string; - - public get tokenStream(): TokenStream; - public set tokenStream(input: TokenStream); - - public override get inputStream(): TokenStream; - public override set inputStream(input: TokenStream); - - public get numberOfSyntaxErrors(): number; - - public getCurrentToken(): Token; - public notifyErrorListeners(msg: string, offendingToken: Token | null, err: RecognitionException | null): void; - public consume(): Token; - public addContextToParseTree(): void; - public enterRule(localctx: ParserRuleContext, state: number, ruleIndex: number): void; - public exitRule(): void; - public enterOuterAlt(localctx: ParserRuleContext, altNum: number): void; - public getPrecedence(): number; - public enterRecursionRule(localctx: ParserRuleContext, state: number, ruleIndex: number, precedence: number): void; - public pushNewRecursionContext(localctx: ParserRuleContext, state: number, ruleIndex: number): void; - public unrollRecursionContexts(parent: ParserRuleContext): void; - public getInvokingContext(ruleIndex: number): ParserRuleContext; - - public precpred(localctx: ParserRuleContext, precedence: number): boolean; - - public isExpectedToken(symbol: number): boolean; - public getExpectedTokens(): IntervalSet; - public getExpectedTokensWithinCurrentRule(): IntervalSet; - public getRuleIndex(ruleName: string): number; - public getRuleInvocationStack(): string[]; - public getDFAStrings(): string[]; - public dumpDFA(): void; - public getSourceName(): string; - public setTrace(trace: boolean): void; - - public createTerminalNode(parent: ParserRuleContext, token: Token,): TerminalNode; - public createErrorNode(parent: ParserRuleContext, token: Token,): ErrorNode; -} diff --git a/src/Parser.js b/src/Parser.ts similarity index 56% rename from src/Parser.js rename to src/Parser.ts index 2e0d5ea..787baea 100644 --- a/src/Parser.js +++ b/src/Parser.ts @@ -4,103 +4,154 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from './Token.js'; -import { TerminalNode } from './tree/TerminalNode.js'; -import { ErrorNode } from './tree/ErrorNode.js'; -import { ErrorNodeImpl } from './tree/ErrorNodeImpl.js'; -import { Recognizer } from './Recognizer.js'; -import { DefaultErrorStrategy } from './DefaultErrorStrategy.js'; -import { ATNDeserializer } from './atn/ATNDeserializer.js'; -import { ATNDeserializationOptions } from './atn/ATNDeserializationOptions.js'; +/* eslint-disable jsdoc/require-returns, jsdoc/require-param, no-underscore-dangle */ + +import { Token } from "./Token.js"; +import { TerminalNode } from "./tree/TerminalNode.js"; +import { ErrorNode } from "./tree/ErrorNode.js"; +import { Recognizer } from "./Recognizer.js"; +import { DefaultErrorStrategy } from "./DefaultErrorStrategy.js"; +import { ATNDeserializer } from "./atn/ATNDeserializer.js"; +import { ATNDeserializationOptions } from "./atn/ATNDeserializationOptions.js"; +import { ParserATNSimulator } from "./atn/ParserATNSimulator.js"; +import { TokenStream } from "./TokenStream.js"; +import { ParseTreeListener } from "./tree/ParseTreeListener.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; +import { TokenFactory } from "./TokenFactory.js"; +import { ATN } from "./atn/ATN.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { RuleTransition } from "./atn/RuleTransition.js"; +import { IntervalSet } from "./misc/IntervalSet.js"; +import { RuleContext } from "./RuleContext.js"; import { TraceListener } from "./TraceListener.js"; -import { TerminalNodeImpl } from "./tree/TerminalNodeImpl.js"; -export class Parser extends Recognizer { +export interface IDebugPrinter { + println(s: string): void; + print(s: string): void; +} + +export abstract class Parser extends Recognizer { + /** For testing only. */ + public printer: IDebugPrinter | null = null; + + /** + * Specifies whether or not the parser should construct a parse tree during + * the parsing process. The default value is {@code true}. + * + * @see #getBuildParseTree + * @see #setBuildParseTree + */ + public buildParseTrees = true; + + /** + * The error handling strategy for the parser. The default value is a new + * instance of {@link DefaultErrorStrategy}. + * + * @see #getErrorHandler + * @see #setErrorHandler + */ + public errorHandler = new DefaultErrorStrategy(); + + /** + * The {@link ParserRuleContext} object for the currently executing rule. + * This is always non-null during the parsing process. + */ + // TODO: make private + public context: ParserRuleContext | null = null; + + /** + * The input stream. + * + * @see #getInputStream + * @see #setInputStream + */ + protected _input: TokenStream | null = null; + + protected _precedenceStack: number[] = []; + + /** + * The list of {@link ParseTreeListener} listeners registered to receive + * events during the parse. + * + * @see #addParseListener + */ + protected _parseListeners: ParseTreeListener[] | null = null; + + /** + * The number of syntax errors reported during parsing. This value is + * incremented each time {@link #notifyErrorListeners} is called. + */ + protected _syntaxErrors = 0; + + /** Indicates parser has matched EOF token. See {@link #exitRule()}. */ + protected matchedEOF = false; + + /** + * When {@link #setTrace}{@code (true)} is called, a reference to the + * {@link TraceListener} is stored here so it can be easily removed in a + * later call to {@link #setTrace}{@code (false)}. The listener itself is + * implemented as a parser listener so this field is not directly used by + * other parser methods. + */ + private _tracer: TraceListener | null = null; + + /** + * This field holds the deserialized {@link ATN} with bypass alternatives, created + * lazily upon first demand. In 4.10 I changed from map + * since we only need one per parser object and also it complicates other targets + * that don't use ATN strings. + * + * @see ATNDeserializationOptions#isGenerateRuleBypassTransitions() + */ + private bypassAltsAtnCache: ATN | null = null; + /** * this is all the parsing support code essentially; most of it is error * recovery stuff. */ - constructor(input) { + public constructor(input: TokenStream) { super(); - // The input stream. - this._input = null; - /** - * The error handling strategy for the parser. The default value is a new - * instance of {@link DefaultErrorStrategy}. - */ - this.errorHandler = new DefaultErrorStrategy(); - this._precedenceStack = []; + this._precedenceStack.push(0); - /** - * The {@link ParserRuleContext} object for the currently executing rule. - * this is always non-null during the parsing process. - */ - this._ctx = null; - /** - * Specifies whether or not the parser should construct a parse tree during - * the parsing process. The default value is {@code true}. - */ - this.buildParseTrees = true; - /** - * When {@link //setTrace}{@code (true)} is called, a reference to the - * {@link TraceListener} is stored here so it can be easily removed in a - * later call to {@link //setTrace}{@code (false)}. The listener itself is - * implemented as a parser listener so this field is not directly used by - * other parser methods. - */ - this._tracer = null; - /** - * The list of {@link ParseTreeListener} listeners registered to receive - * events during the parse. - */ - this._parseListeners = null; - /** - * The number of syntax errors reported during parsing. this value is - * incremented each time {@link //notifyErrorListeners} is called. - */ this._syntaxErrors = 0; this.tokenStream = input; } - get context() { - return this._ctx; - } - // reset the parser's state - reset() { + public reset(): void { if (this._input !== null) { this._input.seek(0); } this.errorHandler.reset(this); - this._ctx = null; + this.context = null; this._syntaxErrors = 0; this.setTrace(false); this._precedenceStack = []; this._precedenceStack.push(0); - if (this.interpreter !== null) { + if (this.interpreter) { this.interpreter.reset(); } } /** * Match current input symbol against {@code ttype}. If the symbol type - * matches, {@link ANTLRErrorStrategy//reportMatch} and {@link //consume} are + * matches, {@link ANTLRErrorStrategy//reportMatch} and {@link consume} are * called to complete the match process. * *

            If the symbol type does not match, * {@link ANTLRErrorStrategy//recoverInline} is called on the current error - * strategy to attempt recovery. If {@link //buildParseTree} is + * strategy to attempt recovery. If {@link buildParseTree} is * {@code true} and the token index of the symbol returned by * {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to * the parse tree by calling {@link ParserRuleContext//addErrorNode}.

            * * @param ttype the token type to match - * @return the matched symbol + * @returns the matched symbol * @throws RecognitionException if the current input symbol did not match * {@code ttype} and the error strategy could not recover from the * mismatched symbol */ - match(ttype) { + public match(ttype: number): Token { let t = this.getCurrentToken(); if (t.type === ttype) { this.errorHandler.reportMatch(this); @@ -111,30 +162,31 @@ export class Parser extends Recognizer { // we must have conjured up a new token during single token // insertion // if it's not the current symbol - this._ctx.addErrorNode(t); + this.context!.addErrorNode(this.createErrorNode(this.context!, t)); } } + return t; } /** * Match current input symbol as a wildcard. If the symbol type matches * (i.e. has a value greater than 0), {@link ANTLRErrorStrategy//reportMatch} - * and {@link //consume} are called to complete the match process. + * and {@link consume} are called to complete the match process. * *

            If the symbol type does not match, * {@link ANTLRErrorStrategy//recoverInline} is called on the current error - * strategy to attempt recovery. If {@link //buildParseTree} is + * strategy to attempt recovery. If {@link buildParseTree} is * {@code true} and the token index of the symbol returned by * {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to * the parse tree by calling {@link ParserRuleContext//addErrorNode}.

            * - * @return the matched symbol + * @returns the matched symbol * @throws RecognitionException if the current input symbol did not match * a wildcard and the error strategy could not recover from the mismatched * symbol */ - matchWildcard() { + public matchWildcard(): Token { let t = this.getCurrentToken(); if (t.type > 0) { this.errorHandler.reportMatch(this); @@ -145,14 +197,15 @@ export class Parser extends Recognizer { // we must have conjured up a new token during single token // insertion // if it's not the current symbol - this._ctx.addErrorNode(t); + this.context!.addErrorNode(this.createErrorNode(this.context!, t)); } } + return t; } - getParseListeners() { - return this._parseListeners || []; + public getParseListeners(): ParseTreeListener[] { + return this._parseListeners ?? []; } /** @@ -184,9 +237,9 @@ export class Parser extends Recognizer { * * @throws NullPointerException if {@code} listener is {@code null} */ - addParseListener(listener) { + public addParseListener(listener: ParseTreeListener): void { if (listener === null) { - throw "listener"; + throw new Error("listener"); } if (this._parseListeners === null) { this._parseListeners = []; @@ -199,10 +252,11 @@ export class Parser extends Recognizer { * *

            If {@code listener} is {@code null} or has not been added as a parse * listener, this method does nothing.

            + * * @param listener the listener to remove */ - removeParseListener(listener) { - if (this._parseListeners !== null) { + public removeParseListener(listener: ParseTreeListener | null): void { + if (this._parseListeners !== null && listener !== null) { const idx = this._parseListeners.indexOf(listener); if (idx >= 0) { this._parseListeners.splice(idx, 1); @@ -214,15 +268,15 @@ export class Parser extends Recognizer { } // Remove all parse listeners. - removeParseListeners() { + public removeParseListeners(): void { this._parseListeners = null; } // Notify any parse listeners of an enter rule event. - triggerEnterRuleEvent() { + public triggerEnterRuleEvent(): void { if (this._parseListeners !== null) { - const ctx = this._ctx; - this._parseListeners.forEach(function (listener) { + const ctx = this.context!; + this._parseListeners.forEach((listener) => { listener.enterEveryRule(ctx); ctx.enterRule(listener); }); @@ -231,26 +285,27 @@ export class Parser extends Recognizer { /** * Notify any parse listeners of an exit rule event. + * * @see //addParseListener */ - triggerExitRuleEvent() { + public triggerExitRuleEvent(): void { if (this._parseListeners !== null) { // reverse order walk of listeners - const ctx = this._ctx; - this._parseListeners.slice(0).reverse().forEach(function (listener) { + const ctx = this.context!; + this._parseListeners.slice(0).reverse().forEach((listener) => { ctx.exitRule(listener); listener.exitEveryRule(ctx); }); } } - getTokenFactory() { - return this._input.tokenSource._factory; + public getTokenFactory(): TokenFactory { + return this._input!.getTokenSource().tokenFactory; } // Tell our token source and error strategy about a new way to create tokens. - setTokenFactory(factory) { - this._input.tokenSource._factory = factory; + public setTokenFactory(factory: TokenFactory): void { + this._input!.getTokenSource().tokenFactory = factory; } /** @@ -258,40 +313,41 @@ export class Parser extends Recognizer { * lazily. * * @throws UnsupportedOperationException if the current parser does not - * implement the {@link //getSerializedATN()} method. + * implement the {@link getSerializedATN()} method. */ - getATNWithBypassAlts() { + public getATNWithBypassAlts(): ATN { const serializedAtn = this.getSerializedATN(); if (serializedAtn === null) { - throw "The current parser does not support an ATN with bypass alternatives."; + throw new Error("The current parser does not support an ATN with bypass alternatives."); } - let result = this.bypassAltsAtnCache[serializedAtn]; - if (result === null) { - const deserializationOptions = new ATNDeserializationOptions(); - deserializationOptions.generateRuleBypassTransitions = true; - result = new ATNDeserializer(deserializationOptions) - .deserialize(serializedAtn); - this.bypassAltsAtnCache[serializedAtn] = result; + + if (this.bypassAltsAtnCache !== null) { + return this.bypassAltsAtnCache; } - return result; + + const deserializationOptions = new ATNDeserializationOptions(); + deserializationOptions.generateRuleBypassTransitions = true; + this.bypassAltsAtnCache = new ATNDeserializer(deserializationOptions).deserialize(serializedAtn); + + return this.bypassAltsAtnCache; } - get tokenStream() { - return this._input; + public get tokenStream(): TokenStream { + return this._input!; } // Set the token stream and reset the parser. - set tokenStream(input) { + public set tokenStream(input: TokenStream) { this._input = null; this.reset(); this._input = input; } - get inputStream() { - return this._input; + public get inputStream(): TokenStream { + return this._input!; } - set inputStream(input) { + public set inputStream(input: TokenStream) { this.tokenStream = input; } @@ -299,22 +355,21 @@ export class Parser extends Recognizer { * Gets the number of syntax errors reported during parsing. This value is * incremented each time {@link notifyErrorListeners} is called. */ - get numberOfSyntaxErrors() { + public get numberOfSyntaxErrors(): number { return this._syntaxErrors; } - /** * Match needs to return the current input symbol, which gets put * into the label for the associated token ref; e.g., x=ID. */ - getCurrentToken() { - return this._input.LT(1); + public getCurrentToken(): Token { + return this._input!.LT(1)!; } - notifyErrorListeners(msg, offendingToken, err) { - offendingToken = offendingToken || null; - err = err || null; + public notifyErrorListeners(msg: string, offendingToken: Token | null, err: RecognitionException | null): void { + offendingToken = offendingToken ?? null; + err = err ?? null; if (offendingToken === null) { offendingToken = this.getCurrentToken(); } @@ -326,7 +381,7 @@ export class Parser extends Recognizer { } /** - * Consume and return the {@link //getCurrentToken current symbol}. + * Consume and return the {@link getCurrentToken current symbol}. * *

            E.g., given the following input with {@code A} being the current * lookahead symbol, this function moves the cursor to {@code B} and returns @@ -346,82 +401,83 @@ export class Parser extends Recognizer { * {@link ParseTreeListener//visitErrorNode} is called on any parse * listeners. */ - consume() { + public consume(): Token { const o = this.getCurrentToken(); if (o.type !== Token.EOF) { this.tokenStream.consume(); } const hasListener = this._parseListeners !== null && this._parseListeners.length > 0; if (this.buildParseTrees || hasListener) { - let node; + let node: ErrorNode | TerminalNode; if (this.errorHandler.inErrorRecoveryMode(this)) { - node = this._ctx.addErrorNode(o); + node = this.context!.addErrorNode(this.createErrorNode(this.context!, o)); } else { - node = this._ctx.addTokenNode(o); + node = this.context!.addTokenNode(o); } - node.invokingState = this.state; + //node.invokingState = this.state; if (hasListener) { - this._parseListeners.forEach(function (listener) { - if (node instanceof ErrorNode || (node.isErrorNode !== undefined && node.isErrorNode())) { + this._parseListeners!.forEach((listener) => { + if (node instanceof ErrorNode) { listener.visitErrorNode(node); - } else if (node instanceof TerminalNode) { + } else { listener.visitTerminal(node); } }); } } + return o; } - addContextToParseTree() { + public addContextToParseTree(): void { // add current context to parent if we have a parent - if (this._ctx.parent !== null) { - this._ctx.parent.addChild(this._ctx); + if (this.context?.parent !== null) { + this.context!.parent.addChild(this.context!); } } /** * Always called by generated parsers upon entry to a rule. Access field - * {@link //_ctx} get the current context. + * {@link context} get the current context. */ - enterRule(localctx, state, ruleIndex) { + public enterRule(localctx: ParserRuleContext, state: number, _ruleIndex: number): void { this.state = state; - this._ctx = localctx; - this._ctx.start = this._input.LT(1); + this.context = localctx; + this.context.start = this._input!.LT(1); if (this.buildParseTrees) { this.addContextToParseTree(); } this.triggerEnterRuleEvent(); } - exitRule() { - this._ctx.stop = this._input.LT(-1); + public exitRule(): void { + this.context!.stop = this._input!.LT(-1); // trigger event on _ctx, before it reverts to parent this.triggerExitRuleEvent(); - this.state = this._ctx.invokingState; - this._ctx = this._ctx.parent; + this.state = this.context!.invokingState; + this.context = this.context!.parent; } - enterOuterAlt(localctx, altNum) { + public enterOuterAlt(localctx: ParserRuleContext, altNum: number): void { localctx.setAltNumber(altNum); // if we have new localctx, make sure we replace existing ctx // that is previous child of parse tree - if (this.buildParseTrees && this._ctx !== localctx) { - if (this._ctx.parent !== null) { - this._ctx.parent.removeLastChild(); - this._ctx.parent.addChild(localctx); + if (this.buildParseTrees && this.context !== localctx) { + if (this.context!.parent !== null) { + this.context!.parent.removeLastChild(); + this.context!.parent.addChild(localctx); } } - this._ctx = localctx; + this.context = localctx; } /** * Get the precedence level for the top-most precedence rule. * - * @return The precedence level for the top-most precedence rule, or -1 if + * @returns The precedence level for the top-most precedence rule, or -1 if * the parser context is not nested within a precedence rule. */ - getPrecedence() { + public getPrecedence(): number { if (this._precedenceStack.length === 0) { return -1; } else { @@ -429,67 +485,68 @@ export class Parser extends Recognizer { } } - enterRecursionRule(localctx, state, ruleIndex, precedence) { + public enterRecursionRule(localctx: ParserRuleContext, state: number, ruleIndex: number, precedence: number): void { this.state = state; this._precedenceStack.push(precedence); - this._ctx = localctx; - this._ctx.start = this._input.LT(1); + this.context = localctx; + this.context.start = this._input!.LT(1); this.triggerEnterRuleEvent(); // simulates rule entry for left-recursive rules } - // Like {@link //enterRule} but for recursive rules. - pushNewRecursionContext(localctx, state, ruleIndex) { - const previous = this._ctx; - previous._parent = localctx; + // Like {@link enterRule} but for recursive rules. + public pushNewRecursionContext(localctx: ParserRuleContext, state: number, _ruleIndex: number): void { + const previous = this.context!; + previous.parent = localctx; previous.invokingState = state; - previous.stop = this._input.LT(-1); + previous.stop = this._input!.LT(-1); - this._ctx = localctx; - this._ctx.start = previous.start; + this.context = localctx; + this.context.start = previous.start; if (this.buildParseTrees) { - this._ctx.addChild(previous); + this.context.addChild(previous); } this.triggerEnterRuleEvent(); // simulates rule entry for left-recursive rules } - unrollRecursionContexts(parent) { + public unrollRecursionContexts(parent: ParserRuleContext | null): void { this._precedenceStack.pop(); - this._ctx.stop = this._input.LT(-1); - const retCtx = this._ctx; // save current ctx (return value) + this.context!.stop = this._input!.LT(-1); + const retCtx = this.context!; // save current ctx (return value) // unroll so _ctx is as it was before call to recursive method const parseListeners = this.getParseListeners(); if (parseListeners !== null && parseListeners.length > 0) { - while (this._ctx !== parent) { + while (this.context !== parent) { this.triggerExitRuleEvent(); - this._ctx = this._ctx.parent; + this.context = this.context!.parent; } } else { - this._ctx = parent; + this.context = parent; } // hook into tree - retCtx._parent = parent; + retCtx.parent = parent; if (this.buildParseTrees && parent !== null) { // add return ctx into invoking rule's tree parent.addChild(retCtx); } } - getInvokingContext(ruleIndex) { - let ctx = this._ctx; + public getInvokingContext(ruleIndex: number): ParserRuleContext | null { + let ctx = this.context; while (ctx !== null) { if (ctx.ruleIndex === ruleIndex) { return ctx; } ctx = ctx.parent; } + return null; } - precpred(localctx, precedence) { + public override precpred(_localctx: ParserRuleContext | null, precedence: number): boolean { return precedence >= this._precedenceStack[this._precedenceStack.length - 1]; } - inContext(context) { + public inContext(_context: string): boolean { // TODO: useful in parser? return false; } @@ -505,13 +562,13 @@ export class Parser extends Recognizer { * * * @param symbol the symbol type to check - * @return {@code true} if {@code symbol} can follow the current state in + * @returns `true` if {@code symbol} can follow the current state in * the ATN, otherwise {@code false}. */ - isExpectedToken(symbol) { + public isExpectedToken(symbol: number): boolean { const atn = this.interpreter.atn; - let ctx = this._ctx; - const s = atn.states[this.state]; + let ctx = this.context; + const s = atn.states[this.state]!; let following = atn.nextTokens(s); if (following.contains(symbol)) { return true; @@ -520,8 +577,8 @@ export class Parser extends Recognizer { return false; } while (ctx !== null && ctx.invokingState >= 0 && following.contains(Token.EPSILON)) { - const invokingState = atn.states[ctx.invokingState]; - const rt = invokingState.transitions[0]; + const invokingState = atn.states[ctx.invokingState]!; + const rt = invokingState.transitions[0] as RuleTransition; following = atn.nextTokens(rt.followState); if (following.contains(symbol)) { return true; @@ -537,25 +594,26 @@ export class Parser extends Recognizer { /** * Computes the set of input symbols which could follow the current parser - * state and context, as given by {@link //getState} and {@link //getContext}, + * state and context, as given by {@link getState} and {@link getContext}, * respectively. * - * @see ATN//getExpectedTokens(int, RuleContext) + * @see ATN.getExpectedTokens(int, RuleContext) */ - getExpectedTokens() { - return this.interpreter.atn.getExpectedTokens(this.state, this._ctx); + public getExpectedTokens(): IntervalSet { + return this.interpreter.atn.getExpectedTokens(this.state, this.context!); } - getExpectedTokensWithinCurrentRule() { + public getExpectedTokensWithinCurrentRule(): IntervalSet { const atn = this.interpreter.atn; - const s = atn.states[this.state]; + const s = atn.states[this.state]!; + return atn.nextTokens(s); } // Get a rule's index (i.e., {@code RULE_ruleName} field) or -1 if not found. - getRuleIndex(ruleName) { + public getRuleIndex(ruleName: string): number { const ruleIndex = this.getRuleIndexMap().get(ruleName); - if (ruleIndex !== null) { + if (ruleIndex != null) { return ruleIndex; } else { return -1; @@ -570,10 +628,10 @@ export class Parser extends Recognizer { * * this is very useful for error messages. */ - getRuleInvocationStack(p) { - p = p || null; + public getRuleInvocationStack(p?: RuleContext | null): string[] { + p = p ?? null; if (p === null) { - p = this._ctx; + p = this.context; } const stack = []; while (p !== null) { @@ -586,39 +644,48 @@ export class Parser extends Recognizer { } p = p.parent; } + return stack; } - // For debugging and other purposes. - getDFAStrings() { + /** + * For debugging and other purposes. + * + * TODO: this differs from the Java version. Change it. + */ + public getDFAStrings(): string { return this.interpreter.decisionToDFA.toString(); } - // For debugging and other purposes. - dumpDFA() { + /** For debugging and other purposes. */ + public dumpDFA(): void { let seenOne = false; - for (let i = 0; i < this.interpreter.decisionToDFA.length; i++) { - const dfa = this.interpreter.decisionToDFA[i]; + for (const dfa of this.interpreter.decisionToDFA) { if (dfa.states.length > 0) { if (seenOne) { console.log(); } - console.log("Decision " + dfa.decision + ":"); - console.log(dfa.toString(this.vocabulary)); + + // During tests this field is assigned. Avoids accessing Node.js stuff outside of the tests. + if (this.printer) { + this.printer.println("Decision " + dfa.decision + ":"); + this.printer.print(dfa.toString(this.vocabulary)); + } + seenOne = true; } } } - getSourceName() { - return this._input.sourceName; + public getSourceName(): string { + return this._input!.getSourceName(); } /** * During a parse is sometimes useful to listen in on the rule entry and exit * events as well as token matches. this is for quick and dirty debugging. */ - setTrace(trace) { + public setTrace(trace: boolean): void { if (!trace) { this.removeParseListener(this._tracer); this._tracer = null; @@ -631,19 +698,11 @@ export class Parser extends Recognizer { } } - createTerminalNode(parent, t) { - return new TerminalNodeImpl(t); + public createTerminalNode(parent: ParserRuleContext, t: Token): TerminalNode { + return new TerminalNode(t); } - createErrorNode(parent, t) { - return new ErrorNodeImpl(t); + public createErrorNode(parent: ParserRuleContext, t: Token): ErrorNode { + return new ErrorNode(t); } } - -/** - * this field maps from the serialized ATN string to the deserialized {@link ATN} with - * bypass alternatives. - * - * @see ATNDeserializationOptions//isGenerateRuleBypassTransitions() - */ -Parser.bypassAltsAtnCache = {}; diff --git a/src/ParserInterpreter.d.ts b/src/ParserInterpreter.d.ts deleted file mode 100644 index 83696a2..0000000 --- a/src/ParserInterpreter.d.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATN } from "./atn/ATN.js"; -import { ATNState } from "./atn/ATNState.js"; -import { BitSet } from "./misc/BitSet.js"; -import { DecisionState } from "./atn/DecisionState.js"; -import { InterpreterRuleContext } from "./InterpreterRuleContext.js"; -import { Parser } from "./Parser.js"; -import { RecognitionException } from "./RecognitionException.js"; -import { Token } from "./Token.js"; -import { TokenStream } from "./TokenStream.js"; -import { Vocabulary } from "./Vocabulary.js"; -import { ParserRuleContext } from "./ParserRuleContext.js"; -import { RuleContext } from "./atn/RuleContext.js"; - -/** - * A parser simulator that mimics what ANTLR's generated - * parser code does. A ParserATNSimulator is used to make - * predictions via adaptivePredict but this class moves a pointer through the - * ATN to simulate parsing. ParserATNSimulator just - * makes us efficient rather than having to backtrack, for example. - * - * This properly creates parse trees even for left recursive rules. - * - * We rely on the left recursive rule invocation and special predicate - * transitions to make left recursive rules work. - * - * See TestParserInterpreter for examples. - */ -export abstract class ParserInterpreter extends Parser { - /** - * This identifies StarLoopEntryState's that begin the (...)* - * precedence loops of left recursive rules. - */ - protected pushRecursionContextStates: BitSet; - - /** - * This stack corresponds to the _parentctx, _parentState pair of locals - * that would exist on call stack frames with a recursive descent parser; - * in the generated function for a left-recursive rule you'd see: - * - * private EContext e(int _p) { - * ParserRuleContext _parentctx = _ctx; // Pair.a - * int _parentState = state; // Pair.b - * ... - * } - * - * Those values are used to create new recursive rule invocation contexts - * associated with left operand of an alt like "expr '*' expr". - */ - protected readonly _parentContextStack: Array<[ParserRuleContext, number]>; - - /** - * We need a map from (decision,inputIndex)->forced alt for computing ambiguous - * parse trees. For now, we allow exactly one override. - */ - protected overrideDecision: number; - protected overrideDecisionInputIndex: number; - protected overrideDecisionAlt: number; - protected overrideDecisionReached: boolean; // latch and only override once; error might trigger infinite loop - - /** - * What is the current context when we override a decisions? This tells - * us what the root of the parse tree is when using override - * for an ambiguity/lookahead check. - */ - protected _overrideDecisionRoot?: InterpreterRuleContext; - - protected _rootContext: InterpreterRuleContext; - - /** - * A copy constructor that creates a new parser interpreter by reusing - * the fields of a previous interpreter. - * - * @param old The interpreter to copy - */ - public constructor(old: ParserInterpreter); - public constructor(grammarFileName: string, vocabulary: Vocabulary, ruleNames: string[], atn: ATN, - input: TokenStream); - public constructor(grammarFileName: ParserInterpreter | string, vocabulary?: Vocabulary, - ruleNames?: string[], atn?: ATN, input?: TokenStream); - - public reset(resetInput?: boolean): void; - - public override get atn(): ATN; - public override get vocabulary(): Vocabulary; - public override get ruleNames(): string[]; - public override get grammarFileName(): string; - - /** - * Begin parsing at startRuleIndex - * - * @param startRuleIndex the grammar rule to start parsing from - * - * @returns the parse tree for the entire input - */ - public parse(startRuleIndex: number): ParserRuleContext; - - public enterRecursionRule(localctx: ParserRuleContext, state: number, ruleIndex: number, precedence: number): void; - - /** - * Override this parser interpreters normal decision-making process - * at a particular decision and input token index. Instead of - * allowing the adaptive prediction mechanism to choose the - * first alternative within a block that leads to a successful parse, - * force it to take the alternative, 1..n for n alternatives. - * - * As an implementation limitation right now, you can only specify one - * override. This is sufficient to allow construction of different - * parse trees for ambiguous input. It means re-parsing the entire input - * in general because you're never sure where an ambiguous sequence would - * live in the various parse trees. For example, in one interpretation, - * an ambiguous input sequence would be matched completely in expression - * but in another it could match all the way back to the root. - * - * s : e '!'? ; - * e : ID - * | ID '!' - * ; - * - * Here, x! can be matched as (s (e ID) !) or (s (e ID !)). In the first - * case, the ambiguous sequence is fully contained only by the root. - * In the second case, the ambiguous sequences fully contained within just - * e, as in: (e ID !). - * - * Rather than trying to optimize this and make - * some intelligent decisions for optimization purposes, I settled on - * just re-parsing the whole input and then using - * {link Trees#getRootOfSubtreeEnclosingRegion} to find the minimal - * subtree that contains the ambiguous sequence. I originally tried to - * record the call stack at the point the parser detected and ambiguity but - * left recursive rules create a parse tree stack that does not reflect - * the actual call stack. That impedance mismatch was enough to make - * it it challenging to restart the parser at a deeply nested rule - * invocation. - * - * Only parser interpreters can override decisions so as to avoid inserting - * override checking code in the critical ALL(*) prediction execution path. - * - * @param decision - * @param tokenIndex - * @param forcedAlt - */ - public addDecisionOverride(decision: number, tokenIndex: number, forcedAlt: number): void; - - protected get atnState(): ATNState; - - protected visitState(p: ATNState): void; - - /** - * Method visitDecisionState() is called when the interpreter reaches - * a decision state (instance of DecisionState). It gives an opportunity - * for subclasses to track interesting things. - * - * @param p : the decision state - * - * @returns The prediction made by the interpreter for this decision state. - */ - protected visitDecisionState(p: DecisionState): number; - - /** - * Provide simple "factory" for InterpreterRuleContext's. - * - * @param parent - * @param invokingStateNumber - * @param ruleIndex - */ - protected createInterpreterRuleContext(parent: ParserRuleContext | undefined, invokingStateNumber: number, - ruleIndex: number): InterpreterRuleContext; - - protected visitRuleStopState(p: ATNState): void; - - /** - * Rely on the error handler for this parser but, if no tokens are consumed - * to recover, add an error node. Otherwise, nothing is seen in the parse - * tree. - * - * @param e - */ - protected recover(e: RecognitionException): void; - - protected recoverInline(): Token; - - /** - * Return the root of the parse, which can be useful if the parser - * bails out. You still can access the top node. Note that, - * because of the way left recursive rules add children, it's possible - * that the root will not have any children if the start rule immediately - * called and left recursive rule that fails. - * - * @since 4.5.1 - */ - public get rootContext(): InterpreterRuleContext; - - public abstract action(localctx: RuleContext | null, ruleIndex: number, actionIndex: number): void; -} diff --git a/src/ParserInterpreter.js b/src/ParserInterpreter.ts similarity index 54% rename from src/ParserInterpreter.js rename to src/ParserInterpreter.ts index 0e684a6..558ef72 100644 --- a/src/ParserInterpreter.js +++ b/src/ParserInterpreter.ts @@ -4,6 +4,8 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable no-underscore-dangle */ + import { ATNState } from "./atn/ATNState.js"; import { BitSet } from "./misc/BitSet.js"; import { FailedPredicateException } from "./FailedPredicateException.js"; @@ -19,29 +21,42 @@ import { ATNStateType } from "./atn/ATNStateType.js"; import { TransitionType } from "./atn/TransitionType.js"; import { DFA } from "./dfa/DFA.js"; import { PredictionContextCache } from "./atn/PredictionContextCache.js"; +import { ATN } from "./atn/ATN.js"; +import { Vocabulary } from "./Vocabulary.js"; +import { TokenStream } from "./TokenStream.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; +import { RuleStartState } from "./atn/RuleStartState.js"; +import { RuleTransition } from "./atn/RuleTransition.js"; +import { PredicateTransition } from "./atn/PredicateTransition.js"; +import { ActionTransition } from "./atn/ActionTransition.js"; +import { PrecedencePredicateTransition } from "./atn/PrecedencePredicateTransition.js"; +import { DecisionState } from "./atn/DecisionState.js"; +import { TokenSource } from "./TokenSource.js"; +import { CharStream } from "./CharStream.js"; export class ParserInterpreter extends Parser { - #grammarFileName; - #atn; - #ruleNames; - #vocabulary; - #decisionToDFA; - #sharedContextCache = new PredictionContextCache(); + protected _rootContext: InterpreterRuleContext; - #pushRecursionContextStates; + protected _parentContextStack: Array<[ParserRuleContext | null, number]> = []; - _rootContext; + protected overrideDecision = -1; + protected overrideDecisionInputIndex = -1; + protected overrideDecisionAlt = -1; + protected overrideDecisionReached = false; - _parentContextStack = []; + protected _overrideDecisionRoot: InterpreterRuleContext | null = null; - overrideDecision = -1; - overrideDecisionInputIndex = -1; - overrideDecisionAlt = -1; - overrideDecisionReached = false; + #grammarFileName: string; + #atn: ATN; + #ruleNames: string[]; + #vocabulary: Vocabulary; + #decisionToDFA: DFA[]; + #sharedContextCache = new PredictionContextCache(); - _overrideDecisionRoot = undefined; + #pushRecursionContextStates; - constructor(grammarFileName, vocabulary, ruleNames, atn, input) { + public constructor(grammarFileName: string, vocabulary: Vocabulary, ruleNames: string[], atn: ATN, + input: TokenStream) { super(input); this.#grammarFileName = grammarFileName; this.#atn = atn; @@ -50,13 +65,13 @@ export class ParserInterpreter extends Parser { // Cache the ATN states where pushNewRecursionContext() must be called in `visitState()`. this.#pushRecursionContextStates = new BitSet(); - for (let state of atn.states) { + for (const state of atn.states) { if (state instanceof StarLoopEntryState && state.precedenceRuleDecision) { this.#pushRecursionContextStates.set(state.stateNumber); } } - this.#decisionToDFA = atn.decisionToState.map(function (ds, i) { + this.#decisionToDFA = atn.decisionToState.map((ds, i) => { return new DFA(ds, i); }); @@ -64,37 +79,37 @@ export class ParserInterpreter extends Parser { this.interpreter = new ParserATNSimulator(this, atn, this.#decisionToDFA, this.#sharedContextCache); } - reset(resetInput) { - super.reset(resetInput); + public override reset(): void { + super.reset(); this.overrideDecisionReached = false; - this._overrideDecisionRoot = undefined; + this._overrideDecisionRoot = null; } - get atn() { + public override get atn(): ATN { return this.#atn; } - get vocabulary() { + public get vocabulary(): Vocabulary { return this.#vocabulary; } - get ruleNames() { + public get ruleNames(): string[] { return this.#ruleNames; } - get grammarFileName() { + public get grammarFileName(): string { return this.#grammarFileName; } - get atnState() { - return this.#atn.states[this.state]; + public get atnState(): ATNState { + return this.#atn.states[this.state]!; } - parse(startRuleIndex) { - let startRuleStartState = this.#atn.ruleToStartState[startRuleIndex]; + public parse(startRuleIndex: number): ParserRuleContext { + const startRuleStartState = this.#atn.ruleToStartState[startRuleIndex]!; - this._rootContext = this.createInterpreterRuleContext(undefined, ATNState.INVALID_STATE_NUMBER, startRuleIndex); + this._rootContext = this.createInterpreterRuleContext(null, ATNState.INVALID_STATE_NUMBER, startRuleIndex); if (startRuleStartState.isPrecedenceRule) { this.enterRecursionRule(this._rootContext, startRuleStartState.stateNumber, startRuleIndex, 0); } @@ -103,19 +118,21 @@ export class ParserInterpreter extends Parser { } while (true) { - let p = this.atnState; + const p = this.atnState; switch (p.stateType) { case ATNStateType.RULE_STOP: // pop; return from rule - if (this._ctx.isEmpty) { + if (this.context?.isEmpty) { if (startRuleStartState.isPrecedenceRule) { - let result = this._ctx; - let parentContext = this._parentContextStack.pop(); + const result = this.context; + const parentContext = this._parentContextStack.pop()!; this.unrollRecursionContexts(parentContext[0]); + return result; } else { this.exitRule(); + return this._rootContext; } } @@ -126,11 +143,10 @@ export class ParserInterpreter extends Parser { default: try { this.visitState(p); - } - catch (e) { + } catch (e) { if (e instanceof RecognitionException) { - this.state = this.#atn.ruleToStopState[p.ruleIndex].stateNumber; - this.context.exception = e; + this.state = this.#atn.ruleToStopState[p.ruleIndex]!.stateNumber; + this.context!.exception = e; this.errorHandler.reportError(this, e); this.recover(e); } else { @@ -143,41 +159,55 @@ export class ParserInterpreter extends Parser { } } - enterRecursionRule(localctx, state, ruleIndex, precedence) { - this._parentContextStack.push([this._ctx, localctx.invokingState]); + public addDecisionOverride(decision: number, tokenIndex: number, forcedAlt: number): void { + this.overrideDecision = decision; + this.overrideDecisionInputIndex = tokenIndex; + this.overrideDecisionAlt = forcedAlt; + } + + public get overrideDecisionRoot(): InterpreterRuleContext | null { + return this._overrideDecisionRoot; + } + + public get rootContext(): InterpreterRuleContext { + return this._rootContext; + } + + public override enterRecursionRule(localctx: ParserRuleContext, state: number, ruleIndex: number, + precedence: number): void { + this._parentContextStack.push([this.context, localctx.invokingState]); super.enterRecursionRule(localctx, state, ruleIndex, precedence); } - visitState(p) { + protected visitState(p: ATNState): void { let predictedAlt = 1; - if (p.transitions.length > 1) { + if (p instanceof DecisionState) { predictedAlt = this.visitDecisionState(p); } - let transition = p.transitions[predictedAlt - 1]; + const transition = p.transitions[predictedAlt - 1]; switch (transition.serializationType) { case TransitionType.EPSILON: if (this.#pushRecursionContextStates.get(p.stateNumber) && !(transition.target instanceof LoopEndState)) { // We are at the start of a left recursive rule's (...)* loop // and we're not taking the exit branch of loop. - let parentContext = this._parentContextStack[this._parentContextStack.length - 1]; - let localctx = - this.createInterpreterRuleContext(parentContext[0], parentContext[1], this._ctx.ruleIndex); - this.pushNewRecursionContext(localctx, - this.#atn.ruleToStartState[p.ruleIndex].stateNumber, - this._ctx.ruleIndex); + const parentContext = this._parentContextStack[this._parentContextStack.length - 1]; + const localctx = + this.createInterpreterRuleContext(parentContext[0], parentContext[1], this.context!.ruleIndex); + this.pushNewRecursionContext(localctx, this.#atn.ruleToStartState[p.ruleIndex]!.stateNumber, + this.context!.ruleIndex); } break; case TransitionType.ATOM: - this.match(transition.label.minElement); + this.match(transition.label!.minElement); break; case TransitionType.RANGE: case TransitionType.SET: case TransitionType.NOT_SET: - if (!transition.matches(this._input.LA(1), Token.MIN_USER_TOKEN_TYPE, 65535)) { + if (!transition.matches(this._input!.LA(1), Token.MIN_USER_TOKEN_TYPE, 65535)) { this.recoverInline(); } this.matchWildcard(); @@ -188,11 +218,12 @@ export class ParserInterpreter extends Parser { break; case TransitionType.RULE: - let ruleStartState = transition.target; - let ruleIndex = ruleStartState.ruleIndex; - let newContext = this.createInterpreterRuleContext(this._ctx, p.stateNumber, ruleIndex); + const ruleStartState = transition.target as RuleStartState; + const ruleIndex = ruleStartState.ruleIndex; + const newContext = this.createInterpreterRuleContext(this.context, p.stateNumber, ruleIndex); if (ruleStartState.isPrecedenceRule) { - this.enterRecursionRule(newContext, ruleStartState.stateNumber, ruleIndex, (transition).precedence); + this.enterRecursionRule(newContext, ruleStartState.stateNumber, ruleIndex, + (transition as RuleTransition).precedence); } else { this.enterRule(newContext, transition.target.stateNumber, ruleIndex); @@ -200,21 +231,21 @@ export class ParserInterpreter extends Parser { break; case TransitionType.PREDICATE: - let predicateTransition = transition; - if (!this.sempred(this._ctx, predicateTransition.ruleIndex, predicateTransition.predIndex)) { + const predicateTransition = transition as PredicateTransition; + if (!this.sempred(this.context, predicateTransition.ruleIndex, predicateTransition.predIndex)) { throw new FailedPredicateException(this); } break; case TransitionType.ACTION: - let actionTransition = transition; - this.action(this._ctx, actionTransition.ruleIndex, actionTransition.actionIndex); + const actionTransition = transition as ActionTransition; + this.action(this.context, actionTransition.ruleIndex, actionTransition.actionIndex); break; case TransitionType.PRECEDENCE: - if (!this.precpred(this._ctx, transition.precedence)) { - let precedence = transition.precedence; + if (!this.precpred(this.context, (transition as PrecedencePredicateTransition).precedence)) { + const precedence = (transition as PrecedencePredicateTransition).precedence; throw new FailedPredicateException(this, `precpred(_ctx, ${precedence})`); } break; @@ -226,68 +257,59 @@ export class ParserInterpreter extends Parser { this.state = transition.target.stateNumber; } - visitDecisionState(p) { + protected visitDecisionState(p: DecisionState): number { let predictedAlt = 1; if (p.transitions.length > 1) { this.errorHandler.sync(this); - let decision = p.decision; - if (decision === this.overrideDecision && this._input.index === this.overrideDecisionInputIndex && + const decision = p.decision; + if (decision === this.overrideDecision && this._input!.index === this.overrideDecisionInputIndex && !this.overrideDecisionReached) { predictedAlt = this.overrideDecisionAlt; this.overrideDecisionReached = true; } else { - predictedAlt = this.interpreter.adaptivePredict(this._input, decision, this._ctx); + predictedAlt = this.interpreter.adaptivePredict(this._input!, decision, this.context); } } return predictedAlt; } - createInterpreterRuleContext(parent, invokingStateNumber, ruleIndex) { + protected createInterpreterRuleContext(parent: ParserRuleContext | null, invokingStateNumber: number, + ruleIndex: number): InterpreterRuleContext { return new InterpreterRuleContext(ruleIndex, parent, invokingStateNumber); } - visitRuleStopState(p) { - let ruleStartState = this.#atn.ruleToStartState[p.ruleIndex]; + protected visitRuleStopState(p: ATNState): void { + const ruleStartState = this.#atn.ruleToStartState[p.ruleIndex]!; if (ruleStartState.isPrecedenceRule) { - let parentContext = this._parentContextStack.pop(); - this.unrollRecursionContexts(parentContext[0]); - this.state = parentContext[1]; + const [parentContext, state] = this._parentContextStack.pop()!; + this.unrollRecursionContexts(parentContext); + this.state = state; } else { this.exitRule(); } - let ruleTransition = this.#atn.states[this.state].transitions[0]; + const ruleTransition = this.#atn.states[this.state]!.transitions[0] as RuleTransition; this.state = ruleTransition.followState.stateNumber; } - addDecisionOverride(decision, tokenIndex, forcedAlt) { - this.overrideDecision = decision; - this.overrideDecisionInputIndex = tokenIndex; - this.overrideDecisionAlt = forcedAlt; - } - - get overrideDecisionRoot() { - return this._overrideDecisionRoot; - } - - recover(e) { - let i = this._input.index; + protected recover(e: RecognitionException): void { + const i = this._input!.index; this.errorHandler.recover(this, e); - if (this._input.index === i) { + if (this._input!.index === i) { // no input consumed, better add an error node - let tok = e.offendingToken; + const tok = e.offendingToken; if (!tok) { throw new Error("Expected exception to have an offending token"); } - const source = tok.getTokenSource(); + const source = tok.tokenSource; const stream = source?.inputStream ?? null; - const sourcePair = [source, stream]; + const sourcePair: [TokenSource | null, CharStream | null] = [source, stream]; if (e instanceof InputMismatchException) { - let expectedTokens = e.getExpectedTokens(); + const expectedTokens = e.getExpectedTokens(); if (!expectedTokens) { throw new Error("Expected the exception to provide expected tokens"); } @@ -298,30 +320,18 @@ export class ParserInterpreter extends Parser { expectedTokenType = expectedTokens.minElement; } - let errToken = - this.getTokenFactory().create(sourcePair, - expectedTokenType, tok.text, - Token.DEFAULT_CHANNEL, - -1, -1, // invalid start/stop - tok.line, tok.charPositionInLine); - this._ctx.addErrorNode(this.createErrorNode(this._ctx, errToken)); + const errToken = this.getTokenFactory().create(sourcePair, expectedTokenType, tok.text, + Token.DEFAULT_CHANNEL, -1, -1, tok.line, tok.column); + this.context!.addErrorNode(this.createErrorNode(this.context!, errToken)); } else { // NoViableAlt - let errToken = - this.getTokenFactory().create(sourcePair, - Token.INVALID_TYPE, tok.text, - Token.DEFAULT_CHANNEL, - -1, -1, // invalid start/stop - tok.line, tok.charPositionInLine); - this._ctx.addErrorNode(this.createErrorNode(this._ctx, errToken)); + const errToken = this.getTokenFactory().create(sourcePair, Token.INVALID_TYPE, tok.text, + Token.DEFAULT_CHANNEL, -1, -1, tok.line, tok.column); + this.context!.addErrorNode(this.createErrorNode(this.context!, errToken)); } } } - recoverInline() { + protected recoverInline(): Token { return this.errorHandler.recoverInline(this); } - - get rootContext() { - return this._rootContext; - } } diff --git a/src/ParserRuleContext.d.ts b/src/ParserRuleContext.d.ts deleted file mode 100644 index dfdc5b8..0000000 --- a/src/ParserRuleContext.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Token } from "./Token.js"; -import { RecognitionException } from "./RecognitionException.js"; -import { ErrorNode } from "./tree/ErrorNode.js"; -import { TerminalNode } from "./tree/TerminalNode.js"; -import { RuleContext } from "./atn/RuleContext.js"; - -export declare class ParserRuleContext extends RuleContext { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static readonly EMPTY: ParserRuleContext; - - public start: Token | null; - public stop: Token | null; - public exception?: RecognitionException; - - public constructor(parent: ParserRuleContext | null, invokingStateNumber: number); - - public get parent(): ParserRuleContext | null; - - public copyFrom(ctx: ParserRuleContext): void; - public addChild(child: RuleContext): void; - public removeLastChild(): void; - public addTokenNode(token: Token): TerminalNode; - public addErrorNode(badToken: Token): ErrorNode; - public getChildCount(): number; - public getChild(i: number): RuleContext | null; - public getToken(ttype: number, i: number): TerminalNode | null; - public getTokens(ttype: number): TerminalNode[]; - public getRuleContext( - index: number, ctxType: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T | null; - public getRuleContexts( - ctxType: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T[]; - - public get ruleIndex(): number; -} diff --git a/src/ParserRuleContext.js b/src/ParserRuleContext.js deleted file mode 100644 index 5a3cb3d..0000000 --- a/src/ParserRuleContext.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { RuleContext } from './atn/RuleContext.js'; -import { TerminalNode } from './tree/TerminalNode.js'; -import { TerminalNodeImpl } from './tree/TerminalNodeImpl.js'; -import { ErrorNodeImpl } from './tree/ErrorNodeImpl.js'; -import { Interval } from "./misc/Interval.js"; - -/** - * A rule invocation record for parsing. - * - * Contains all of the information about the current rule not stored in the - * RuleContext. It handles parse tree children list, Any ATN state - * tracing, and the default values available for rule indications: - * start, stop, rule index, current alt number, current - * ATN state. - * - * Subclasses made for each rule and grammar track the parameters, - * return values, locals, and labels specific to that rule. These - * are the objects that are returned from rules. - * - * Note text is not an actual field of a rule return value; it is computed - * from start and stop using the input stream's toString() method. I - * could add a ctor to this so that we can pass in and store the input - * stream, but I'm not sure we want to do that. It would seem to be undefined - * to get the .text property anyway if the rule matches tokens from multiple - * input streams. - * - * I do not use getters for fields of objects that are used simply to - * group values such as this aggregate. The getters/setters are there to - * satisfy the superclass interface. - */ -export class ParserRuleContext extends RuleContext { - - constructor(parent, invokingStateNumber) { - super(parent, invokingStateNumber); - /** - * If we are debugging or building a parse tree for a visitor, - * we need to track all of the tokens and rule invocations associated - * with this rule's context. This is empty for parsing w/o tree constr. - * operation because we don't the need to track the details about - * how we parse this rule. - */ - this.children = null; - this.start = null; - this.stop = null; - /** - * The exception that forced this rule to return. If the rule successfully - * completed, this is {@code null}. - */ - this.exception = null; - } - - // COPY a ctx (I'm deliberately not using copy constructor) - copyFrom(ctx) { - // from RuleContext - this._parent = ctx._parent; - this.invokingState = ctx.invokingState; - this.children = null; - this.start = ctx.start; - this.stop = ctx.stop; - // copy any error nodes to alt label node - if (ctx.children) { - this.children = []; - // reset parent pointer for any error nodes - ctx.children.map(function (child) { - if (child instanceof ErrorNodeImpl) { - this.children.push(child); - child.parent = this; - } - }, this); - } - } - - // Double dispatch methods for listeners - enterRule(listener) { - } - - exitRule(listener) { - } - - // Does not set parent link; other add methods do that - addChild(child) { - if (this.children === null) { - this.children = []; - } - this.children.push(child); - return child; - } - - /** Used by enterOuterAlt to toss out a RuleContext previously added as - * we entered a rule. If we have // label, we will need to remove - * generic ruleContext object. - */ - removeLastChild() { - if (this.children !== null) { - this.children.pop(); - } - } - - addTokenNode(token) { - const node = new TerminalNodeImpl(token); - this.addChild(node); - node.parent = this; - return node; - } - - addErrorNode(badToken) { - const node = new ErrorNodeImpl(badToken); - this.addChild(node); - node.parent = this; - return node; - } - - getChild(i, type) { - type = type ?? null; - if (this.children === null || i < 0 || i >= this.children.length) { - return null; - } - if (type === null) { - return this.children[i]; - } else { - for (let j = 0; j < this.children.length; j++) { - const child = this.children[j]; - if (child instanceof type) { - if (i === 0) { - return child; - } else { - i -= 1; - } - } - } - return null; - } - } - - getToken(ttype, i) { - if (this.children === null || i < 0 || i >= this.children.length) { - return null; - } - for (let j = 0; j < this.children.length; j++) { - const child = this.children[j]; - if (child instanceof TerminalNode) { - if (child.symbol.type === ttype) { - if (i === 0) { - return child; - } else { - i -= 1; - } - } - } - } - return null; - } - - getTokens(ttype) { - if (this.children === null) { - return []; - } else { - const tokens = []; - for (let j = 0; j < this.children.length; j++) { - const child = this.children[j]; - if (child instanceof TerminalNode) { - if (child.symbol.type === ttype) { - tokens.push(child); - } - } - } - return tokens; - } - } - - getRuleContext(i, ctxType) { - return this.getChild(i, ctxType); - } - - getRuleContexts(ctxType) { - if (this.children === null) { - return []; - } else { - const contexts = []; - for (let j = 0; j < this.children.length; j++) { - const child = this.children[j]; - if (child instanceof ctxType) { - contexts.push(child); - } - } - return contexts; - } - } - - getChildCount() { - if (this.children === null) { - return 0; - } else { - return this.children.length; - } - } - - getSourceInterval() { - if (this.start === null || this.stop === null) { - return Interval.INVALID_INTERVAL; - } else { - return new Interval(this.start.tokenIndex, this.stop.tokenIndex); - } - } -} - -RuleContext.EMPTY = new ParserRuleContext(); diff --git a/src/ParserRuleContext.ts b/src/ParserRuleContext.ts new file mode 100644 index 0000000..cdd6b21 --- /dev/null +++ b/src/ParserRuleContext.ts @@ -0,0 +1,259 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable jsdoc/require-returns, jsdoc/require-param */ + +import { RuleContext } from "./RuleContext.js"; +import { Interval } from "./misc/Interval.js"; +import { Token } from "./Token.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { ParseTreeListener } from "./tree/ParseTreeListener.js"; +import { ParseTree } from "./tree/ParseTree.js"; +import { TerminalNode } from "./tree/TerminalNode.js"; +import { ErrorNode } from "./tree/ErrorNode.js"; + +/** + * A rule invocation record for parsing. + * + * Contains all of the information about the current rule not stored in the + * RuleContext. It handles parse tree children list, Any ATN state + * tracing, and the default values available for rule indications: + * start, stop, rule index, current alt number, current + * ATN state. + * + * Subclasses made for each rule and grammar track the parameters, + * return values, locals, and labels specific to that rule. These + * are the objects that are returned from rules. + * + * Note text is not an actual field of a rule return value; it is computed + * from start and stop using the input stream's toString() method. I + * could add a ctor to this so that we can pass in and store the input + * stream, but I'm not sure we want to do that. It would seem to be undefined + * to get the .text property anyway if the rule matches tokens from multiple + * input streams. + * + * I do not use getters for fields of objects that are used simply to + * group values such as this aggregate. The getters/setters are there to + * satisfy the superclass interface. + */ +export class ParserRuleContext extends RuleContext { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly EMPTY = new ParserRuleContext(); + + public start: Token | null = null; + public stop: Token | null = null; + + /** + * The exception that forced this rule to return. If the rule successfully + * completed, this is {@code null}. + */ + public exception: RecognitionException | null = null; + + public constructor(); + public constructor(parent: ParserRuleContext | null, invokingStateNumber: number); + public constructor(parent?: ParserRuleContext | null, invokingStateNumber?: number) { + if (!parent) { + super(); + } else { + super(parent, invokingStateNumber); + } + + this.start = null; + this.stop = null; + } + + public override get parent(): ParserRuleContext | null { + return super.parent as ParserRuleContext | null; + } + + public override set parent(parent: ParserRuleContext | null) { + super.parent = parent; + } + + // COPY a ctx (I'm deliberately not using copy constructor) + public copyFrom(ctx: ParserRuleContext): void { + // from RuleContext + this.parent = ctx.parent; + this.invokingState = ctx.invokingState; + this.children = null; + this.start = ctx.start; + this.stop = ctx.stop; + // copy any error nodes to alt label node + if (ctx.children) { + this.children = []; + // reset parent pointer for any error nodes + ctx.children.forEach((child) => { + if (child instanceof ErrorNode) { + this.children!.push(child); + child.parent = this; + } + }, this); + } + } + + // Double dispatch methods for listeners + public enterRule(_listener: ParseTreeListener): void { + } + + public exitRule(_listener: ParseTreeListener): void { + } + + /** + * Add a parse tree node to this as a child. Works for + * internal and leaf nodes. Does not set parent link; + * other add methods must do that. Other addChild methods + * call this. + * + * We cannot set the parent pointer of the incoming node + * because the existing interfaces do not have a setParent() + * method and I don't want to break backward compatibility for this. + */ + public addAnyChild(t: T): T { + if (this.children == null) { + this.children = []; + } + this.children.push(t); + + return t; + } + + public addChild(child: RuleContext): RuleContext { + return this.addAnyChild(child); + } + + /** + * Used by enterOuterAlt to toss out a RuleContext previously added as + * we entered a rule. If we have // label, we will need to remove + * generic ruleContext object. + */ + public removeLastChild(): void { + if (this.children !== null) { + this.children.pop(); + } + } + + public addTokenNode(token: Token): TerminalNode { + const node = new TerminalNode(token); + this.addAnyChild(node); + node.parent = this; + + return node; + } + + /** + * Add a child to this node based upon badToken. It + * creates a ErrorNode rather than using + * {@link Parser#createErrorNode(ParserRuleContext, Token)}. I'm leaving this + * in for compatibility but the parser doesn't use this anymore. + * + * @deprecated + */ + public addErrorNode(errorNode: ErrorNode): ErrorNode { + errorNode.parent = this; + + return this.addAnyChild(errorNode); + } + + public override getChild(i: number): RuleContext | null; + public override getChild(i: number, + type: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T | null; + public override getChild(i: number, + type?: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T | null { + if (this.children === null || i < 0 || i >= this.children.length) { + return null; + } + + if (!type) { + return this.children[i] as T; + } else { + for (const child of this.children) { + if (child instanceof type) { + if (i === 0) { + return child; + } else { + i -= 1; + } + } + } + + return null; + } + } + + public getToken(ttype: number, i: number): TerminalNode | null { + if (this.children === null || i < 0 || i >= this.children.length) { + return null; + } + + for (const child of this.children) { + if (child instanceof TerminalNode) { + if (child.symbol?.type === ttype) { + if (i === 0) { + return child; + } else { + i -= 1; + } + } + } + } + + return null; + } + + public getTokens(ttype: number): TerminalNode[] { + if (this.children === null) { + return []; + } else { + const tokens = []; + for (const child of this.children) { + if (child instanceof TerminalNode) { + if (child.symbol?.type === ttype) { + tokens.push(child); + } + } + } + + return tokens; + } + } + + public getRuleContext(index: number, + ctxType: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T | null { + return this.getChild(index, ctxType); + } + + public getRuleContexts( + ctxType: new (parent: ParserRuleContext | null, invokingStateNumber: number) => T): T[] { + if (this.children === null) { + return []; + } else { + const contexts = []; + for (const child of this.children) { + if (child instanceof ctxType) { + contexts.push(child); + } + } + + return contexts; + } + } + + public override getChildCount(): number { + if (this.children === null) { + return 0; + } else { + return this.children.length; + } + } + + public override getSourceInterval(): Interval { + if (this.start === null || this.stop === null) { + return Interval.INVALID_INTERVAL; + } else { + return new Interval(this.start.tokenIndex, this.stop.tokenIndex); + } + } +} diff --git a/src/ProxyErrorListener.js b/src/ProxyErrorListener.js deleted file mode 100644 index ed1abcb..0000000 --- a/src/ProxyErrorListener.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BaseErrorListener } from "./BaseErrorListener.js"; - -export class ProxyErrorListener extends BaseErrorListener { - constructor(delegates) { - super(); - if (delegates === null) { - throw "delegates"; - } - this.delegates = delegates; - return this; - } - - syntaxError(recognizer, offendingSymbol, line, column, msg, e) { - this.delegates.map(d => d.syntaxError(recognizer, offendingSymbol, line, column, msg, e)); - } - - reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) { - this.delegates.map(d => d.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs)); - } - - reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) { - this.delegates.map(d => d.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs)); - } - - reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) { - this.delegates.map(d => d.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs)); - } -} diff --git a/src/ProxyErrorListener.ts b/src/ProxyErrorListener.ts new file mode 100644 index 0000000..ef52df2 --- /dev/null +++ b/src/ProxyErrorListener.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { BaseErrorListener } from "./BaseErrorListener.js"; +import { Parser } from "./Parser.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { Recognizer } from "./Recognizer.js"; +import { Token } from "./Token.js"; +import { ATNConfigSet } from "./atn/ATNConfigSet.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; +import { DFA } from "./dfa/DFA.js"; +import { BitSet } from "./misc/BitSet.js"; + +export class ProxyErrorListener extends BaseErrorListener { + public constructor(private delegates: BaseErrorListener[]) { + super(); + + return this; + } + + public override syntaxError(recognizer: Recognizer, + offendingSymbol: S | null, line: number, column: number, msg: string, e: RecognitionException | null): void { + this.delegates.forEach((d) => { + d.syntaxError(recognizer, offendingSymbol, line, column, msg, e); + }); + } + + public override reportAmbiguity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, exact: boolean, + ambigAlts: BitSet | null, configs: ATNConfigSet): void { + this.delegates.forEach((d) => { + d.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs); + }); + } + + public override reportAttemptingFullContext(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, + conflictingAlts: BitSet | null, configs: ATNConfigSet): void { + this.delegates.forEach((d) => { + d.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs); + }); + } + + public override reportContextSensitivity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, + prediction: number, configs: ATNConfigSet): void { + this.delegates.forEach((d) => { + d.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs); + }); + } +} diff --git a/src/RecognitionException.d.ts b/src/RecognitionException.d.ts deleted file mode 100644 index e6ced92..0000000 --- a/src/RecognitionException.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from "./CharStream.js"; -import { Recognizer } from "./Recognizer.js"; -import { Token } from "./Token.js"; -import { TokenStream } from "./TokenStream.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { ParserRuleContext } from "./ParserRuleContext.js"; -import { RuleContext } from "./atn/RuleContext.js"; -import { IntervalSet } from "./misc/IntervalSet.js"; - -export interface IExceptionParams { - message: string; - recognizer: Recognizer | null; - input: CharStream | TokenStream | null; - ctx: ParserRuleContext | null; -} - -export declare class RecognitionException extends Error { - public ctx: RuleContext; - public offendingToken: Token | null; - - public constructor(params: IExceptionParams); - - public getExpectedTokens(): IntervalSet | null; -} diff --git a/src/RecognitionException.js b/src/RecognitionException.js deleted file mode 100644 index 24cb865..0000000 --- a/src/RecognitionException.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * The root of the ANTLR exception hierarchy. In general, ANTLR tracks just - * 3 kinds of errors: prediction errors, failed predicate errors, and - * mismatched input errors. In each case, the parser knows where it is - * in the input, where it is in the ATN, the rule invocation stack, - * and what kind of problem occurred. - */ -export class RecognitionException extends Error { - constructor(params) { - super(params.message); - if (Error.captureStackTrace) - Error.captureStackTrace(this, RecognitionException); - this.message = params.message; - this.recognizer = params.recognizer; - this.input = params.input; - this.ctx = params.ctx; - - /** - * The current {@link Token} when an error occurred. Since not all streams - * support accessing symbols by index, we have to track the {@link Token} - * instance itself - */ - this.offendingToken = null; - - /** - * Get the ATN state number the parser was in at the time the error - * occurred. For {@link NoViableAltException} and - * {@link LexerNoViableAltException} exceptions, this is the - * {@link DecisionState} number. For others, it is the state whose outgoing - * edge we couldn't match. - */ - this.offendingState = -1; - if (this.recognizer !== null) { - this.offendingState = this.recognizer.state; - } - } - - /** - * Gets the set of input symbols which could potentially follow the - * previously matched symbol at the time this exception was thrown. - * - *

            If the set of expected tokens is not known and could not be computed, - * this method returns {@code null}.

            - * - * @return The set of token types that could potentially follow the current - * state in the ATN, or {@code null} if the information is not available. - */ - getExpectedTokens() { - if (this.recognizer !== null) { - return this.recognizer.atn.getExpectedTokens(this.offendingState, this.ctx); - } else { - return null; - } - } - - //

            If the state number is not known, this method returns -1.

            - toString() { - return this.message; - } -} diff --git a/src/RecognitionException.ts b/src/RecognitionException.ts new file mode 100644 index 0000000..b8ad296 --- /dev/null +++ b/src/RecognitionException.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { CharStream } from "./CharStream.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; +import { Recognizer } from "./Recognizer.js"; +import { RuleContext } from "./RuleContext.js"; +import { Token } from "./Token.js"; +import { TokenStream } from "./TokenStream.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; +import { IntervalSet } from "./misc/IntervalSet.js"; + +export interface IExceptionParams { + message: string; + recognizer: Recognizer | null; + input: CharStream | TokenStream | null; + ctx: ParserRuleContext | null; +} + +/** + * The root of the ANTLR exception hierarchy. In general, ANTLR tracks just + * 3 kinds of errors: prediction errors, failed predicate errors, and + * mismatched input errors. In each case, the parser knows where it is + * in the input, where it is in the ATN, the rule invocation stack, + * and what kind of problem occurred. + */ +export class RecognitionException extends Error { + public ctx: RuleContext | null; + + /** + * The current {@link Token} when an error occurred. Since not all streams + * support accessing symbols by index, we have to track the {@link Token} + * instance itself + */ + public offendingToken: Token | null = null; + + /** + * Get the ATN state number the parser was in at the time the error + * occurred. For {@link NoViableAltException} and + * {@link LexerNoViableAltException} exceptions, this is the + * {@link DecisionState} number. For others, it is the state whose outgoing + * edge we couldn't match. + */ + public offendingState = -1; + + protected recognizer: Recognizer | null; + protected input: CharStream | TokenStream | null; + + public constructor(params: IExceptionParams) { + super(params.message); + if (Error.captureStackTrace) { Error.captureStackTrace(this, RecognitionException); } + this.message = params.message; + this.recognizer = params.recognizer; + this.input = params.input; + this.ctx = params.ctx; + + if (this.recognizer !== null) { + this.offendingState = this.recognizer.state; + } + } + + /** + * Gets the set of input symbols which could potentially follow the + * previously matched symbol at the time this exception was thrown. + * + *

            If the set of expected tokens is not known and could not be computed, + * this method returns {@code null}.

            + * + * @returns The set of token types that could potentially follow the current + * state in the ATN, or {@code null} if the information is not available. + */ + public getExpectedTokens(): IntervalSet | null { + if (this.recognizer !== null && this.ctx !== null) { + return this.recognizer.atn.getExpectedTokens(this.offendingState, this.ctx); + } else { + return null; + } + } + + //

            If the state number is not known, this method returns -1.

            + public override toString(): string { + return this.message; + } +} diff --git a/src/Recognizer.d.ts b/src/Recognizer.d.ts deleted file mode 100644 index 49f20e7..0000000 --- a/src/Recognizer.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntStream } from "./IntStream.js"; -import { Token } from "./Token.js"; -import { Vocabulary } from "./Vocabulary.js"; -import { ATN } from "./atn/ATN.js"; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { RuleContext } from "./atn/RuleContext.js"; -import { BaseErrorListener } from "./BaseErrorListener.js"; -import { RecognitionException } from "./RecognitionException.js"; - -export declare abstract class Recognizer { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static readonly EOF: number; - - public interpreter: ATNInterpreter; - - public addErrorListener(listener: BaseErrorListener): void; - public removeErrorListeners(): void; - - public getTokenTypeMap(): Map>; - public getRuleIndexMap(): Map>; - - public getTokenType(tokenName: string): number; - public getErrorHeader(e: RecognitionException): string; - public getTokenErrorDisplay(t: Token | null): string; - - public sempred(_localctx: RuleContext | null, ruleIndex: number, actionIndex: number): boolean; - public precpred(localctx: RuleContext | null, precedence: number): boolean; - - public get atn(): ATN; - - public get state(): number; - public set state(newState: number); - - protected checkVersion(toolVersion: string): void; - - public abstract get inputStream(): IntStream; - public abstract set inputStream(input: IntStream); - - // TODO: remove need for this: public abstract get literalNames(): Array; - // TODO: remove need for this: public abstract get symbolicNames(): Array; - public abstract get grammarFileName(): string; - public abstract get ruleNames(): string[]; - public abstract get vocabulary(): Vocabulary; -} diff --git a/src/Recognizer.js b/src/Recognizer.js deleted file mode 100644 index c99f43d..0000000 --- a/src/Recognizer.js +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Token } from './Token.js'; -import { ConsoleErrorListener } from './ConsoleErrorListener.js'; -import { ProxyErrorListener } from './ProxyErrorListener.js'; - -export class Recognizer { - static EOF = -1; - static tokenTypeMapCache = new Map(); - static ruleIndexMapCache = new Map(); - - constructor() { - this._listeners = [ConsoleErrorListener.INSTANCE]; - this.interpreter = null; - this._stateNumber = -1; - } - - checkVersion(toolVersion) { - const runtimeVersion = "4.13.1"; - if (runtimeVersion !== toolVersion) { - console.log("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion); - } - } - - addErrorListener(listener) { - this._listeners.push(listener); - } - - removeErrorListeners() { - this._listeners = []; - } - - getTokenTypeMap() { - const vocabulary = this.vocabulary; - - let result = Recognizer.tokenTypeMapCache.get(vocabulary); - if (!result) { - result = new Map(); - for (let i = 0; i <= this.atn.maxTokenType; i++) { - const literalName = vocabulary.getLiteralName(i); - if (literalName) { - result.set(literalName, i); - } - - const symbolicName = vocabulary.getSymbolicName(i); - if (symbolicName) { - result.set(symbolicName, i); - } - } - - result.set("EOF", Token.EOF); - - Recognizer.tokenTypeMapCache.set(vocabulary, result); - } - return result; - } - - /** - * Get a map from rule names to rule indexes. - *

            Used for XPath and tree pattern compilation.

            - */ - getRuleIndexMap() { - const ruleNames = this.ruleNames; - let result = Recognizer.ruleIndexMapCache.get(ruleNames); - if (!result) { - result = new Map(); - ruleNames.forEach((ruleName, idx) => result.set(ruleName, idx)); - - Recognizer.ruleIndexMapCache.set(ruleNames, result); - } - - return result; - } - - getTokenType(tokenName) { - const ttype = this.getTokenTypeMap().get(tokenName); - if (ttype) { - return ttype; - } - - return Token.INVALID_TYPE; - } - - // What is the error header, normally line/character position information? - getErrorHeader(e) { - const line = e.offendingToken.line; - const column = e.offendingToken.column; - return "line " + line + ":" + column; - } - - /** - * How should a token be displayed in an error message? The default - * is to display just the text, but during development you might - * want to have a lot of information spit out. Override in that case - * to use t.toString() (which, for CommonToken, dumps everything about - * the token). This is better than forcing you to override a method in - * your token objects because you don't have to go modify your lexer - * so that it creates a new Java type. - * - * @deprecated This method is not called by the ANTLR 4 Runtime. Specific - * implementations of {@link ANTLRErrorStrategy} may provide a similar - * feature when necessary. For example, see - * {@link DefaultErrorStrategy//getTokenErrorDisplay}.*/ - getTokenErrorDisplay(t) { - if (t === null) { - return ""; - } - let s = t.text; - if (s === null) { - if (t.type === Token.EOF) { - s = ""; - } else { - s = "<" + t.type + ">"; - } - } - s = s.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); - return "'" + s + "'"; - } - - getErrorListenerDispatch() { - return new ProxyErrorListener(this._listeners); - } - - /** - * subclass needs to override these if there are sempreds or actions - * that the ATN interp needs to execute - */ - sempred(localctx, ruleIndex, actionIndex) { - return true; - } - - precpred(localctx, precedence) { - return true; - } - - get atn() { - return this.interpreter.atn; - } - - get state() { - return this._stateNumber; - } - - set state(state) { - this._stateNumber = state; - } -} diff --git a/src/Recognizer.ts b/src/Recognizer.ts new file mode 100644 index 0000000..5b6eff8 --- /dev/null +++ b/src/Recognizer.ts @@ -0,0 +1,153 @@ +/* eslint-disable jsdoc/require-param */ +/* eslint-disable jsdoc/require-returns */ +/* eslint-disable jsdoc/require-param-description */ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Token } from "./Token.js"; +import { ConsoleErrorListener } from "./ConsoleErrorListener.js"; +import { ProxyErrorListener } from "./ProxyErrorListener.js"; +import { ATNSimulator } from "./atn/ATNSimulator.js"; +import { Vocabulary } from "./Vocabulary.js"; +import { ANTLRErrorListener } from "./ANTLRErrorListener.js"; +import { RecognitionException } from "./RecognitionException.js"; +import { ATN } from "./atn/ATN.js"; +import { RuleContext } from "./RuleContext.js"; +import { IntStream } from "./IntStream.js"; + +export abstract class Recognizer { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly EOF = -1; + + private static tokenTypeMapCache = new Map>(); + private static ruleIndexMapCache = new Map>(); + + public interpreter: ATNInterpreter; + + #listeners: ANTLRErrorListener[] = [ConsoleErrorListener.INSTANCE]; + #stateNumber = -1; + + public checkVersion(toolVersion: string): void { + const runtimeVersion = "4.13.1"; + if (runtimeVersion !== toolVersion) { + console.error("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion); + } + } + + public addErrorListener(listener: ANTLRErrorListener): void { + this.#listeners.push(listener); + } + + public removeErrorListeners(): void { + this.#listeners = []; + } + + public getTokenTypeMap(): Map { + const vocabulary = this.vocabulary; + + let result = Recognizer.tokenTypeMapCache.get(vocabulary); + if (!result) { + result = new Map(); + for (let i = 0; i <= this.atn.maxTokenType; i++) { + const literalName = vocabulary.getLiteralName(i); + if (literalName) { + result.set(literalName, i); + } + + const symbolicName = vocabulary.getSymbolicName(i); + if (symbolicName) { + result.set(symbolicName, i); + } + } + + result.set("EOF", Token.EOF); + + Recognizer.tokenTypeMapCache.set(vocabulary, result); + } + + return result; + } + + /** + * Get a map from rule names to rule indexes. + *

            Used for XPath and tree pattern compilation.

            + */ + public getRuleIndexMap(): Map { + const ruleNames = this.ruleNames; + let result = Recognizer.ruleIndexMapCache.get(ruleNames); + if (!result) { + result = new Map(); + ruleNames.forEach((ruleName, idx) => { + return result!.set(ruleName, idx); + }); + + Recognizer.ruleIndexMapCache.set(ruleNames, result); + } + + return result; + } + + public getTokenType(tokenName: string): number { + const ttype = this.getTokenTypeMap().get(tokenName); + if (ttype) { + return ttype; + } + + return Token.INVALID_TYPE; + } + + /** What is the error header, normally line/character position information? */ + public getErrorHeader(e: RecognitionException): string { + const line = e.offendingToken?.line; + const column = e.offendingToken?.column; + + return "line " + line + ":" + column; + } + + public getErrorListenerDispatch(): ANTLRErrorListener { + return new ProxyErrorListener(this.#listeners); + } + + /** + * subclass needs to override these if there are semantic predicates or actions + * that the ATN interp needs to execute + */ + public sempred(_localctx: RuleContext | null, _ruleIndex: number, _actionIndex: number): boolean { + return true; + } + + public precpred(_localctx: RuleContext | null, _precedence: number): boolean { + return true; + } + + public action(_localctx: RuleContext | null, _ruleIndex: number, _actionIndex: number): void { + } + + public get atn(): ATN { + return this.interpreter.atn; + } + + public get state(): number { + return this.#stateNumber; + } + + public set state(state: number) { + this.#stateNumber = state; + } + + public getSerializedATN(): number[] { + throw new Error("there is no serialized ATN"); + } + + // TODO: remove need for this: public abstract get literalNames(): Array; + // TODO: remove need for this: public abstract get symbolicNames(): Array; + public abstract get grammarFileName(): string; + public abstract get ruleNames(): string[]; + public abstract get vocabulary(): Vocabulary; + + public abstract get inputStream(): IntStream; + public abstract set inputStream(input: IntStream); +} diff --git a/src/atn/RuleContext.js b/src/RuleContext.ts similarity index 59% rename from src/atn/RuleContext.js rename to src/RuleContext.ts index 6ada5e5..ab5c5bb 100644 --- a/src/atn/RuleContext.js +++ b/src/RuleContext.ts @@ -1,15 +1,34 @@ /* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ +* Copyright (c) The ANTLR Project. All rights reserved. +* Use of this file is governed by the BSD 3-clause license that +* can be found in the LICENSE.txt file in the project root. +*/ -import { Interval } from '../misc/Interval.js'; -import { ParseTree } from "../tree/ParseTree.js"; -import { Trees } from '../tree/Trees.js'; +/* eslint-disable jsdoc/require-param, jsdoc/require-returns */ -export class RuleContext extends ParseTree { - /** A rule context is a record of a single rule invocation. It knows +import { Parser } from "./Parser.js"; +import { Interval } from "./misc/Interval.js"; +import { ParseTree } from "./tree/ParseTree.js"; +import { ParseTreeVisitor } from "./tree/ParseTreeVisitor.js"; +import { Trees } from "./tree/Trees.js"; +import { ATN } from "./atn/ATN.js"; + +export class RuleContext implements ParseTree { + // TODO: move to ParserRuleContext. + public children: ParseTree[] | null = null; + + /** + * What state invoked the rule associated with this context? + * The "return address" is the followState of invokingState + * If parent is null, this should be -1 this context object represents + * the start rule. + */ + public invokingState: number; + + #parent: RuleContext | null = null; + + /** + * A rule context is a record of a single rule invocation. It knows * which context invoked it, if any. If there is no parent context, then * naturally the invoking state is not valid. The parent link * provides a chain upwards from the current rule invocation to the root @@ -29,34 +48,29 @@ export class RuleContext extends ParseTree { * * @see ParserRuleContext */ - constructor(parent, invokingState) { - super(); - this._parent = parent ?? null; - this.children = null; - - /** - * What state invoked the rule associated with this context? - * The "return address" is the followState of invokingState - * If parent is null, this should be -1. - */ + public constructor(parent?: RuleContext | null, invokingState?: number) { + this.parent = parent ?? null; this.invokingState = invokingState ?? -1; } - get parent() { - return this._parent; + public get parent(): RuleContext | null { + return this.#parent; } - set parent(value) { - this._parent = value; + public set parent(parent: RuleContext | null) { + this.#parent = parent; } - depth() { + public depth(): number { let n = 0; - let p = this; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + let p: RuleContext | null = this; while (p !== null) { p = p.parent; n += 1; } + return n; } @@ -64,24 +78,24 @@ export class RuleContext extends ParseTree { * A context is empty if there is no invoking state; meaning nobody call * current context. */ - isEmpty() { + public isEmpty(): boolean { return this.invokingState === -1; } // satisfy the ParseTree / SyntaxTree interface - getSourceInterval() { + public getSourceInterval(): Interval { return Interval.INVALID_INTERVAL; } - get ruleContext() { + public get ruleContext(): RuleContext { return this; } - get ruleIndex() { + public get ruleIndex(): number { return -1; } - getPayload() { + public getPayload(): RuleContext { return this; } @@ -93,11 +107,11 @@ export class RuleContext extends ParseTree { * added to the parse trees, they will not appear in the output of this * method. */ - getText() { - if (this.getChildCount() === 0) { + public getText(): string { + if (!this.children || this.getChildCount() === 0) { return ""; } else { - return this.children.map(function (child) { + return this.children.map((child) => { return child.getText(); }).join(""); } @@ -111,9 +125,8 @@ export class RuleContext extends ParseTree { * option contextSuperClass. * to set it. */ - getAltNumber() { - // use constant value of ATN.INVALID_ALT_NUMBER to avoid circular dependency - return 0; + public getAltNumber(): number { + return ATN.INVALID_ALT_NUMBER; } /** @@ -123,22 +136,18 @@ export class RuleContext extends ParseTree { * a subclass of ParserRuleContext with backing field and set * option contextSuperClass. */ - setAltNumber(altNumber) { + public setAltNumber(_altNumber: number): void { } - setParent(parent) { - this.parent = parent; - } - - getChild(i) { + public getChild(_i: number): ParseTree | null { return null; } - getChildCount() { + public getChildCount(): number { return 0; } - accept(visitor) { + public accept(visitor: ParseTreeVisitor): T | null { return visitor.visitChildren(this); } @@ -146,14 +155,16 @@ export class RuleContext extends ParseTree { * Print out a whole tree, not just a node, in LISP format * (root child1 .. childN). Print just a node if this is a leaf. */ - toStringTree(ruleNames, recog) { + public toStringTree(ruleNames: string[], recog: Parser): string { return Trees.toStringTree(this, ruleNames, recog); } - toString(ruleNames, stop) { - ruleNames = ruleNames || null; - stop = stop || null; - let p = this; + public toString(ruleNames?: string[] | null, stop?: RuleContext | null): string { + ruleNames = ruleNames ?? null; + stop = stop ?? null; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + let p: RuleContext | null = this; let s = "["; while (p !== null && p !== stop) { if (ruleNames === null) { @@ -172,6 +183,7 @@ export class RuleContext extends ParseTree { p = p.parent; } s += "]"; + return s; } } diff --git a/src/Token.d.ts b/src/Token.d.ts deleted file mode 100644 index 37d85a6..0000000 --- a/src/Token.d.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from "./CharStream.js"; -import { TokenSource } from "./TokenSource.js"; - -export declare class Token { - /* eslint-disable @typescript-eslint/naming-convention */ - - public static INVALID_TYPE: number; - - /** - * During lookahead operations, this "token" signifies we hit rule end ATN state - * and did not follow it despite needing to. - */ - public static EPSILON: number; - - public static MIN_USER_TOKEN_TYPE: number; - - /** - * All tokens go to the parser (unless skip() is called in that rule) - * on a particular "channel". The parser tunes to a particular channel - * so that whitespace etc... can go to the parser on a "hidden" channel. - */ - public static DEFAULT_CHANNEL: number; - - /** - * Anything on different channel than DEFAULT_CHANNEL is not parsed - * by parser. - */ - public static HIDDEN_CHANNEL: number; - - public static EOF: number; - - /* eslint-enable @typescript-eslint/naming-convention */ - - public tokenIndex: number; - public line: number; - public column: number; - public channel: number; - public text: string; - public type: number; - public start: number; - public stop: number; - - public clone(): Token; - public cloneWithType(type: number): Token; - - public getTokenSource(): TokenSource | null; - public getInputStream(): CharStream; -} diff --git a/src/Token.js b/src/Token.js deleted file mode 100644 index 31e548c..0000000 --- a/src/Token.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * A token has properties: text, type, line, character position in the line - * (so we can ignore tabs), token channel, index, and source from which - * we obtained this token. - */ -export class Token { - - constructor() { - this.source = null; - this.type = null; // token type of the token - this.channel = null; // The parser ignores everything not on DEFAULT_CHANNEL - this.start = null; // optional; return -1 if not implemented. - this.stop = null; // optional; return -1 if not implemented. - this.tokenIndex = null; // from 0..n-1 of the token object in the input stream - this.line = null; // line=1..n of the 1st character - this.column = null; // beginning of the line at which it occurs, 0..n-1 - this._text = null; // text of the token. - } - - getTokenSource() { - return this.source[0]; - } - - getInputStream() { - return this.source[1]; - } - - get text() { - return this._text; - } - - set text(text) { - this._text = text; - } -} - -Token.INVALID_TYPE = 0; - -/** - * During lookahead operations, this "token" signifies we hit rule end ATN state - * and did not follow it despite needing to. - */ -Token.EPSILON = -2; - -Token.MIN_USER_TOKEN_TYPE = 1; - -Token.EOF = -1; - -/** - * All tokens go to the parser (unless skip() is called in that rule) - * on a particular "channel". The parser tunes to a particular channel - * so that whitespace etc... can go to the parser on a "hidden" channel. - */ -Token.DEFAULT_CHANNEL = 0; - -/** - * Anything on different channel than DEFAULT_CHANNEL is not parsed - * by parser. - */ -Token.HIDDEN_CHANNEL = 1; diff --git a/src/Token.ts b/src/Token.ts new file mode 100644 index 0000000..742334f --- /dev/null +++ b/src/Token.ts @@ -0,0 +1,124 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { CharStream } from "./CharStream.js"; +import { IntStream } from "./IntStream.js"; +import { TokenSource } from "./TokenSource.js"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +/** + * A token has properties: text, type, line, character position in the line + * (so we can ignore tabs), token channel, index, and source from which + * we obtained this token. + */ +export interface Token { + /** + * Get the text of the token. + */ + text: string | null; + + /** Get the token type of the token */ + type: number; + + /** + * The line number on which the 1st character of this token was matched, + * line=1..n + */ + line: number; + + /** + * The index of the first character of this token relative to the + * beginning of the line at which it occurs, 0..n-1 + */ + column: number; + + /** + * Return the channel this token. Each token can arrive at the parser + * on a different channel, but the parser only "tunes" to a single channel. + * The parser ignores everything not on DEFAULT_CHANNEL. + */ + channel: number; + + /** + * An index from 0..n-1 of the token object in the input stream. + * This must be valid in order to print token streams and + * use TokenRewriteStream. + * + * Return -1 to indicate that this token was conjured up since + * it doesn't have a valid index. + */ + tokenIndex: number; + + /** + * The starting character index of the token + * This method is optional; return -1 if not implemented. + */ + start: number; + + /** + * The last character index of the token. + * This method is optional; return -1 if not implemented. + */ + stop: number; + + /** + Gets the {@link TokenSource} which created this token. + */ + get tokenSource(): TokenSource | null; + + /** + * Gets the {@link CharStream} from which this token was derived. + */ + get inputStream(): CharStream | null; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Token { + export const INVALID_TYPE = 0; + + /** + * During lookahead operations, this "token" signifies we hit rule end ATN state + * and did not follow it despite needing to. + */ + export const EPSILON = -2; + + export const MIN_USER_TOKEN_TYPE = 1; + + export const EOF = IntStream.EOF; + + /** + * All tokens go to the parser (unless skip() is called in that rule) + * on a particular "channel". The parser tunes to a particular channel + * so that whitespace etc... can go to the parser on a "hidden" channel. + */ + export const DEFAULT_CHANNEL = 0; + + /** + * Anything on different channel than DEFAULT_CHANNEL is not parsed + * by parser. + */ + export const HIDDEN_CHANNEL = 1; + + /** + * This is the minimum constant value which can be assigned to a + * user-defined token channel. + * + *

            + * The non-negative numbers less than {@link #MIN_USER_CHANNEL_VALUE} are + * assigned to the predefined channels {@link #DEFAULT_CHANNEL} and + * {@link #HIDDEN_CHANNEL}.

            + * + * @see Token#getChannel() + */ + export const MIN_USER_CHANNEL_VALUE = 2; +} + +export const isToken = (candidate: unknown): candidate is Token => { + const token = candidate as Token; + + return token.tokenSource !== undefined && token.channel !== undefined; +}; diff --git a/src/TokenFactory.js b/src/TokenFactory.js deleted file mode 100644 index 3c034b1..0000000 --- a/src/TokenFactory.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class TokenFactory { - create(source, type, text, channel, start, stop, line, column) { } -} diff --git a/src/TokenFactory.d.ts b/src/TokenFactory.ts similarity index 72% rename from src/TokenFactory.d.ts rename to src/TokenFactory.ts index bc2d6c5..4071e2e 100644 --- a/src/TokenFactory.d.ts +++ b/src/TokenFactory.ts @@ -4,16 +4,16 @@ * can be found in the LICENSE.txt file in the project root. */ -import { InputStream } from "./InputStream.js"; +import { CharStream } from "./CharStream.js"; import { Token } from "./Token.js"; import { TokenSource } from "./TokenSource.js"; -export declare class TokenFactory { +export interface TokenFactory { /** * This is the method used to create tokens in the lexer and in the * error handling strategy. If text!=null, than the start and stop positions * are wiped to -1 in the text override is set in the CommonToken. */ - public create(source: [TokenSource | null, InputStream | null], type: number, text: string | null, channel: number, + create(source: [TokenSource | null, CharStream | null], type: number, text: string | null, channel: number, start: number, stop: number, line: number, charPositionInLine: number): Symbol; } diff --git a/src/TokenSource.d.ts b/src/TokenSource.d.ts deleted file mode 100644 index 0d0085d..0000000 --- a/src/TokenSource.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { CharStream } from "./CharStream.js"; - -export declare class TokenSource { - public readonly inputStream: CharStream | undefined; -} diff --git a/src/TokenSource.js b/src/TokenSource.js deleted file mode 100644 index 1d759b2..0000000 --- a/src/TokenSource.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class TokenSource { } diff --git a/src/TokenSource.ts b/src/TokenSource.ts new file mode 100644 index 0000000..8f01858 --- /dev/null +++ b/src/TokenSource.ts @@ -0,0 +1,85 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { CharStream } from "./CharStream.js"; +import { Token } from "./Token.js"; +import { TokenFactory } from "./TokenFactory.js"; + +/** + * A source of tokens must provide a sequence of tokens via {@link #nextToken()} + * and also must reveal it's source of characters; {@link CommonToken}'s text is + * computed from a {@link CharStream}; it only store indices into the char + * stream. + * + *

            Errors from the lexer are never passed to the parser. Either you want to keep + * going or you do not upon token recognition error. If you do not want to + * continue lexing then you do not want to continue parsing. Just throw an + * exception not under {@link RecognitionException} and Java will naturally toss + * you all the way out of the recognizers. If you want to continue lexing then + * you should not throw an exception to the parser--it has already requested a + * token. Keep lexing until you get a valid one. Just report errors and keep + * going, looking for a valid token.

            + */ +export interface TokenSource { + /** + * Return a {@link Token} object from your input stream (usually a + * {@link CharStream}). Do not fail/return upon lexing error; keep chewing + * on the characters until you get a good one; errors are not passed through + * to the parser. + */ + nextToken(): Token; + + /** + * Get the line number for the current position in the input stream. The + * first line in the input is line 1. + * + @returns The line number for the current position in the input stream, or + * 0 if the current token source does not track line numbers. + */ + line: number; + + /** + * Get the index into the current line for the current position in the input + * stream. The first character on a line has position 0. + * + @returns The line number for the current position in the input stream, or + * -1 if the current token source does not track character positions. + */ + _tokenStartColumn: number; + + /** + * Get the {@link CharStream} from which this token source is currently + * providing tokens. + * + @returns The {@link CharStream} associated with the current position in + * the input, or {@code null} if no input stream is available for the token + * source. + */ + inputStream: CharStream | null; + + /** + * Gets the name of the underlying input source. This method returns a + * non-null, non-empty string. If such a name is not known, this method + * returns {@link IntStream#UNKNOWN_SOURCE_NAME}. + */ + sourceName: string; + + /** + * Set the {@link TokenFactory} this token source should use for creating + * {@link Token} objects from the input. + * + * @param factory The {@link TokenFactory} to use for creating tokens. + */ + set tokenFactory(factory: TokenFactory); + + /** + * Gets the {@link TokenFactory} this token source is currently using for + * creating {@link Token} objects from the input. + * + @returns The {@link TokenFactory} currently used by this token source. + */ + get tokenFactory(): TokenFactory; +} diff --git a/src/TokenStream.js b/src/TokenStream.js deleted file mode 100644 index f4455cf..0000000 --- a/src/TokenStream.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -// Interface in Java. -export class TokenStream { } diff --git a/src/TokenStream.d.ts b/src/TokenStream.ts similarity index 59% rename from src/TokenStream.d.ts rename to src/TokenStream.ts index 9790540..d1e82a5 100644 --- a/src/TokenStream.d.ts +++ b/src/TokenStream.ts @@ -5,6 +5,7 @@ */ import { IntStream } from "./IntStream.js"; +import { RuleContext } from "./RuleContext.js"; import { Token } from "./Token.js"; import { TokenSource } from "./TokenSource.js"; import { Interval } from "./misc/Interval.js"; @@ -12,7 +13,7 @@ import { Interval } from "./misc/Interval.js"; /** * An IntStream whose symbols are {@link Token} instances. */ -export declare interface TokenStream extends IntStream { +export interface TokenStream extends IntStream { /** * Get the {@link Token} instance associated with the value returned by * {@link #LA LA(k)}. This method has the same pre- and post-conditions as @@ -23,7 +24,7 @@ export declare interface TokenStream extends IntStream { * @see IntStream#LA */ // eslint-disable-next-line @typescript-eslint/naming-convention - LT(k: number): Token; + LT(k: number): Token | null; /** * Gets the {@link Token} at the specified {@code index} in the stream. When @@ -88,4 +89,55 @@ export declare interface TokenStream extends IntStream { * @returns The text of all tokens in the stream. */ getText(): string; + + /** + * Return the text of all tokens in the source interval of the specified + * context. This method behaves like the following code, including potential + * exceptions from the call to {@link #getText(Interval)}, but may be + * optimized by the specific implementation. + * + *

            If {@code ctx.getSourceInterval()} does not return a valid interval of + * tokens provided by this stream, the behavior is unspecified.

            + * + *
            +     * TokenStream stream = ...;
            +     * String text = stream.getText(ctx.getSourceInterval());
            +     * 
            + * + * @param ctx The context providing the source interval of tokens to get + * text for. + * @returns The text of all tokens within the source interval of {@code ctx}. + */ + getText(ctx: RuleContext): string; + + /** + * Return the text of all tokens in this stream between {@code start} and + * {@code stop} (inclusive). + * + *

            If the specified {@code start} or {@code stop} token was not provided by + * this stream, or if the {@code stop} occurred before the {@code start} + * token, the behavior is unspecified.

            + * + *

            For streams which ensure that the {@link Token#getTokenIndex} method is + * accurate for all of its provided tokens, this method behaves like the + * following code. Other streams may implement this method in other ways + * provided the behavior is consistent with this at a high level.

            + * + *
            +     * TokenStream stream = ...;
            +     * String text = "";
            +     * for (int i = start.getTokenIndex(); i <= stop.getTokenIndex(); i++) {
            +     *   text += stream.get(i).getText();
            +     * }
            +     * 
            + * + * @param start The first token in the interval to get text for. + * @param stop The last token in the interval to get text for (inclusive). + * @returns The text of all tokens lying between the specified {@code start} + * and {@code stop} tokens. + * + * @throws UnsupportedOperationException if this stream does not support + * this method for the specified tokens + */ + getText(start: Token | null, stop: Token | null): string; } diff --git a/src/TokenStreamRewriter.d.ts b/src/TokenStreamRewriter.d.ts deleted file mode 100644 index 6f83540..0000000 --- a/src/TokenStreamRewriter.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/* eslint-disable max-classes-per-file */ - -import { CommonTokenStream } from "./CommonTokenStream.js"; -import { Token } from "./Token.js"; -import { Interval } from "./misc/Interval.js"; - -type Rewrites = Array; -type Text = unknown; - -export declare class TokenStreamRewriter { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static DEFAULT_PROGRAM_NAME: string; - - public constructor(tokens: CommonTokenStream); - public getTokenStream(): CommonTokenStream; - public insertAfter(token: Token, text: Text, programName?: string): void; - public insertAfter(index: number, text: Text, programName?: string): void; - public insertBefore(token: Token, text: Text, programName?: string): void; - public insertBefore(index: number, text: Text, programName?: string): void; - public replaceSingle(token: Token, text: Text, programName?: string): void; - public replaceSingle(index: number, text: Text, programName?: string): void; - public replace(from: Token | number, to: Token | number, text: Text, programName?: string): void; - public delete(from: number | Token, to?: number | Token, programName?: string): void; - public getProgram(name: string): Rewrites; - public initializeProgram(name: string): Rewrites; - public getText(): string; - public getText(program: string): string; - public getText(interval: Interval, programName?: string): string; - public reduceToSingleOperationPerIndex(rewrites: Rewrites): Map; - public catOpText(a: Text, b: Text): string; - public getKindOfOps(rewrites: Rewrites, kind: unknown, before: number): RewriteOperation[]; -} - -declare class RewriteOperation { - public tokens: CommonTokenStream; - public instructionIndex: number; - public index: number; - public text: Text; - - public constructor(tokens: CommonTokenStream, index: number, instructionIndex: number, text: Text); - - public toString(): string; -} diff --git a/src/TokenStreamRewriter.js b/src/TokenStreamRewriter.ts similarity index 50% rename from src/TokenStreamRewriter.js rename to src/TokenStreamRewriter.ts index e9942fb..b0dbe4e 100644 --- a/src/TokenStreamRewriter.js +++ b/src/TokenStreamRewriter.ts @@ -4,43 +4,123 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable max-classes-per-file, jsdoc/require-param, @typescript-eslint/naming-convention */ +/* eslint-disable jsdoc/require-returns */ + import { Token } from "./Token.js"; +import { TokenStream } from "./TokenStream.js"; import { Interval } from "./misc/Interval.js"; /** - * @typedef {import("./CommonTokenStream").default} CommonTokenStream - * @typedef {Array} Rewrites - * @typedef {unknown} Text + * Useful for rewriting out a buffered input token stream after doing some + * augmentation or other manipulations on it. + * + *

            + * You can insert stuff, replace, and delete chunks. Note that the operations + * are done lazily--only if you convert the buffer to a {@link String} with + * {@link TokenStream#getText()}. This is very efficient because you are not + * moving data around all the time. As the buffer of tokens is converted to + * strings, the {@link #getText()} method(s) scan the input token stream and + * check to see if there is an operation at the current index. If so, the + * operation is done and then normal {@link String} rendering continues on the + * buffer. This is like having multiple Turing machine instruction streams + * (programs) operating on a single input tape. :)

            + * + *

            + * This rewriter makes no modifications to the token stream. It does not ask the + * stream to fill itself up nor does it advance the input cursor. The token + * stream {@link TokenStream#index()} will return the same value before and + * after any {@link #getText()} call.

            + * + *

            + * The rewriter only works on tokens that you have in the buffer and ignores the + * current input cursor. If you are buffering tokens on-demand, calling + * {@link #getText()} halfway through the input will only do rewrites for those + * tokens in the first half of the file.

            + * + *

            + * Since the operations are done lazily at {@link #getText}-time, operations do + * not screw up the token index values. That is, an insert operation at token + * index {@code i} does not change the index values for tokens + * {@code i}+1..n-1.

            + * + *

            + * Because operations never actually alter the buffer, you may always get the + * original token stream back without undoing anything. Since the instructions + * are queued up, you can easily simulate transactions and roll back any changes + * if there is an error just by removing instructions. For example,

            + * + *
            + * CharStream input = new ANTLRFileStream("input");
            + * TLexer lex = new TLexer(input);
            + * CommonTokenStream tokens = new CommonTokenStream(lex);
            + * T parser = new T(tokens);
            + * TokenStreamRewriter rewriter = new TokenStreamRewriter(tokens);
            + * parser.startRule();
            + * 
            + * + *

            + * Then in the rules, you can execute (assuming rewriter is visible):

            + * + *
            + * Token t,u;
            + * ...
            + * rewriter.insertAfter(t, "text to put after t");}
            + * rewriter.insertAfter(u, "text after u");}
            + * System.out.println(rewriter.getText());
            + * 
            + * + *

            + * You can also have multiple "instruction streams" and get multiple rewrites + * from a single pass over the input. Just name the instruction streams and use + * that name again when printing the buffer. This could be useful for generating + * a C file and also its header file--all from the same buffer:

            + * + *
            + * rewriter.insertAfter("pass1", t, "text to put after t");}
            + * rewriter.insertAfter("pass2", u, "text after u");}
            + * System.out.println(rewriter.getText("pass1"));
            + * System.out.println(rewriter.getText("pass2"));
            + * 
            + * + *

            + * If you don't use named rewrite streams, a "default" stream is used as the + * first example shows.

            */ - export class TokenStreamRewriter { - // eslint-disable-next-line no-undef - static DEFAULT_PROGRAM_NAME = "default"; + public static readonly DEFAULT_PROGRAM_NAME = "default"; + public static readonly PROGRAM_INIT_SIZE = 100; + public static readonly MIN_TOKEN_INDEX = 0; + + /** Our source stream */ + protected readonly tokens: TokenStream; /** - * @param {CommonTokenStream} tokens The token stream to modify + * You may have multiple, named streams of rewrite operations. + * I'm calling these things "programs." + * Maps String (name) -> rewrite (List) */ - constructor(tokens) { - this.tokens = tokens; - /** @type {Map} */ - this.programs = new Map(); - } + protected readonly programs = new Map(); + + /** Map String (program name) -> Integer index */ + protected readonly lastRewriteTokenIndexes: Map; /** - * @returns {CommonTokenStream} + * @param tokens The token stream to modify */ - getTokenStream() { + public constructor(tokens: TokenStream) { + this.tokens = tokens; + } + + public getTokenStream(): TokenStream { return this.tokens; } /** * Insert the supplied text after the specified token (or token index) - * @param {Token | number} tokenOrIndex - * @param {Text} text - * @param {string} [programName] */ - insertAfter(tokenOrIndex, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { - /** @type {number} */ + public insertAfter(tokenOrIndex: Token | number, text: string, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): void { let index; if (typeof tokenOrIndex === "number") { index = tokenOrIndex; @@ -49,19 +129,16 @@ export class TokenStreamRewriter { } // to insert after, just insert before next index (even if past end) - let rewrites = this.getProgram(programName); - let op = new InsertAfterOp(this.tokens, index, rewrites.length, text); + const rewrites = this.getProgram(programName); + const op = new InsertAfterOp(this.tokens, index, rewrites.length, text); rewrites.push(op); } /** * Insert the supplied text before the specified token (or token index) - * @param {Token | number} tokenOrIndex - * @param {Text} text - * @param {string} [programName] */ - insertBefore(tokenOrIndex, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { - /** @type {number} */ + public insertBefore(tokenOrIndex: Token | number, text: unknown, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): void { let index; if (typeof tokenOrIndex === "number") { index = tokenOrIndex; @@ -76,22 +153,17 @@ export class TokenStreamRewriter { /** * Replace the specified token with the supplied text - * @param {Token | number} tokenOrIndex - * @param {Text} text - * @param {string} [programName] */ - replaceSingle(tokenOrIndex, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { + public replaceSingle(tokenOrIndex: Token | number, text: string, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): void { this.replace(tokenOrIndex, tokenOrIndex, text, programName); } /** * Replace the specified range of tokens with the supplied text - * @param {Token | number} from - * @param {Token | number} to - * @param {Text} text - * @param {string} [programName] */ - replace(from, to, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { + public replace(from: Token | number, to: Token | number, text: string | null, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): void { if (typeof from !== "number") { from = from.tokenIndex; } @@ -101,53 +173,43 @@ export class TokenStreamRewriter { if (from > to || from < 0 || to < 0 || to >= this.tokens.size) { throw new RangeError(`replace: range invalid: ${from}..${to}(size=${this.tokens.size})`); } - let rewrites = this.getProgram(programName); - let op = new ReplaceOp(this.tokens, from, to, rewrites.length, text); + const rewrites = this.getProgram(programName); + const op = new ReplaceOp(this.tokens, from, to, rewrites.length, text); rewrites.push(op); } /** * Delete the specified range of tokens - * @param {number | Token} from - * @param {number | Token} to - * @param {string} [programName] */ - delete(from, to, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { + public delete(from: Token | number, to?: Token | number | null, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): void { if (to == null) { to = from; } this.replace(from, to, null, programName); } - /** - * @param {string} name - * @returns {Rewrites} - */ - getProgram(name) { + public getProgram(name: string): RewriteOperation[] { let is = this.programs.get(name); if (is == null) { is = this.initializeProgram(name); } + return is; } - /** - * @param {string} name - * @returns {Rewrites} - */ - initializeProgram(name) { - const is = []; + public initializeProgram(name: string): RewriteOperation[] { + const is: RewriteOperation[] = []; this.programs.set(name, is); + return is; } /** - * Return the text from the original tokens altered per the instructions given to this rewriter - * @param {Interval | string} [intervalOrProgram] - * @param {string} [programName] - * @returns {string} + * @returns the text from the original tokens altered per the instructions given to this rewriter */ - getText(intervalOrProgram, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) { + public getText(intervalOrProgram?: Interval | string, + programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME): string { let interval; if (intervalOrProgram instanceof Interval) { interval = intervalOrProgram; @@ -175,17 +237,17 @@ export class TokenStreamRewriter { return this.tokens.getText(new Interval(start, stop)); // no instructions to execute } - let buf = []; + const buf = []; // First, optimize instruction stream - let indexToOp = this.reduceToSingleOperationPerIndex(rewrites); + const indexToOp = this.reduceToSingleOperationPerIndex(rewrites); // Walk buffer, executing instructions and emitting tokens let i = start; while (i <= stop && i < this.tokens.size) { - let op = indexToOp.get(i); + const op = indexToOp.get(i); indexToOp.delete(i); // remove so any left have index size-1 - let t = this.tokens.get(i); + const t = this.tokens.get(i); if (op == null) { // no operation at that index, just dump token if (t.type !== Token.EOF) { @@ -205,8 +267,8 @@ export class TokenStreamRewriter { // Scan any remaining operations after last token // should be included (they will be inserts). for (const op of indexToOp.values()) { - if (op.index >= this.tokens.size - 1) { - buf.push(op.text.toString()); + if (op && op.index >= this.tokens.size - 1) { + buf.push(String(op.text)); } } } @@ -215,51 +277,50 @@ export class TokenStreamRewriter { } /** - * @param {Rewrites} rewrites - * @returns {Map} a map from token index to operation + * @returns a map from token index to operation */ - reduceToSingleOperationPerIndex(rewrites) { + protected reduceToSingleOperationPerIndex( + rewrites: Array): Map { // WALK REPLACES for (let i = 0; i < rewrites.length; i++) { - let op = rewrites[i]; + const op = rewrites[i]; if (op == null) { continue; } if (!(op instanceof ReplaceOp)) { continue; } - let rop = op; + const rop = op; // Wipe prior inserts within range - let inserts = this.getKindOfOps(rewrites, InsertBeforeOp, i); - for (let iop of inserts) { + const inserts = this.getKindOfOps(rewrites, InsertBeforeOp, i); + for (const iop of inserts) { if (iop.index === rop.index) { // E.g., insert before 2, delete 2..2; update replace // text to include insert before, kill insert - rewrites[iop.instructionIndex] = undefined; - rop.text = iop.text.toString() + (rop.text != null ? rop.text.toString() : ""); + rewrites[iop.instructionIndex] = null; + rop.text = String(iop.text) + (rop.text != null ? rop.text.toString() : ""); } else if (iop.index > rop.index && iop.index <= rop.lastIndex) { // delete insert as it's a no-op. - rewrites[iop.instructionIndex] = undefined; + rewrites[iop.instructionIndex] = null; } } // Drop any prior replaces contained within - let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i); - for (let prevRop of prevReplaces) { - if (prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex) { + const prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i); + for (const prevRop of prevReplaces) { + if (prevRop.index >= rop.index && (prevRop as ReplaceOp).lastIndex <= rop.lastIndex) { // delete replace as it's a no-op. - rewrites[prevRop.instructionIndex] = undefined; + rewrites[prevRop.instructionIndex] = null; continue; } // throw exception unless disjoint or identical - let disjoint = - prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex; + const disjoint = (prevRop as ReplaceOp).lastIndex < rop.index || prevRop.index > rop.lastIndex; // Delete special case of replace (text==null): // D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right) if (prevRop.text == null && rop.text == null && !disjoint) { - rewrites[prevRop.instructionIndex] = undefined; // kill first delete + rewrites[prevRop.instructionIndex] = null; // kill first delete rop.index = Math.min(prevRop.index, rop.index); - rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex); + rop.lastIndex = Math.max((prevRop as ReplaceOp).lastIndex, rop.lastIndex); } else if (!disjoint) { throw new Error(`replace op boundaries of ${rop} overlap with previous ${prevRop}`); @@ -269,48 +330,47 @@ export class TokenStreamRewriter { // WALK INSERTS for (let i = 0; i < rewrites.length; i++) { - let op = rewrites[i]; + const op = rewrites[i]; if (op == null) { continue; } if (!(op instanceof InsertBeforeOp)) { continue; } - let iop = op; + const iop = op; // combine current insert with prior if any at same index - let prevInserts = this.getKindOfOps(rewrites, InsertBeforeOp, i); - for (let prevIop of prevInserts) { + const prevInserts = this.getKindOfOps(rewrites, InsertBeforeOp, i); + for (const prevIop of prevInserts) { if (prevIop.index === iop.index) { if (prevIop instanceof InsertAfterOp) { iop.text = this.catOpText(prevIop.text, iop.text); - rewrites[prevIop.instructionIndex] = undefined; + rewrites[prevIop.instructionIndex] = null; } else if (prevIop instanceof InsertBeforeOp) { // combine objects // convert to strings...we're in process of toString'ing // whole token buffer so no lazy eval issue with any templates iop.text = this.catOpText(iop.text, prevIop.text); // delete redundant prior insert - rewrites[prevIop.instructionIndex] = undefined; + rewrites[prevIop.instructionIndex] = null; } } } // look for replaces where iop.index is in range; error - let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i); - for (let rop of prevReplaces) { + const prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i); + for (const rop of prevReplaces) { if (iop.index === rop.index) { rop.text = this.catOpText(iop.text, rop.text); - rewrites[i] = undefined; // delete current insert + rewrites[i] = null; // delete current insert continue; } - if (iop.index >= rop.index && iop.index <= rop.lastIndex) { + if (iop.index >= rop.index && iop.index <= (rop as ReplaceOp).lastIndex) { throw new Error(`insert op ${iop} within boundaries of previous ${rop}`); } } } - /** @type {Map} */ - let m = new Map(); - for (let op of rewrites) { + const m = new Map(); + for (const op of rewrites) { if (op == null) { // ignore deleted ops continue; @@ -320,15 +380,11 @@ export class TokenStreamRewriter { } m.set(op.index, op); } + return m; } - /** - * @param {Text} a - * @param {Text} b - * @returns {string} - */ - catOpText(a, b) { + private catOpText(a: unknown, b: unknown): string { let x = ""; let y = ""; if (a != null) { @@ -337,59 +393,60 @@ export class TokenStreamRewriter { if (b != null) { y = b.toString(); } + return x + y; } /** * Get all operations before an index of a particular kind - * @param {Rewrites} rewrites - * @param {any} kind - * @param {number} before */ - getKindOfOps(rewrites, kind, before) { - return rewrites.slice(0, before).filter(op => op && op instanceof kind); + private getKindOfOps(rewrites: Array, + kind: new (...args: unknown[]) => T, before: number): T[] { + return rewrites.slice(0, before).filter((op) => { + return op && op instanceof kind; + }) as T[]; } } class RewriteOperation { - /** - * @param {CommonTokenStream} tokens - * @param {number} index - * @param {number} instructionIndex - * @param {Text} text - */ - constructor(tokens, index, instructionIndex, text) { + /** What index into rewrites List are we? */ + public instructionIndex: number; + /** Token buffer index. */ + public index: number; + public text: unknown; + + public tokens: TokenStream; + + public constructor(tokens: TokenStream, index: number, instructionIndex: number, text: unknown) { this.tokens = tokens; this.instructionIndex = instructionIndex; this.index = index; this.text = text === undefined ? "" : text; } - toString() { + public execute(_buf: string[]): number { + return this.index; + } + + public toString() { let opName = this.constructor.name; const $index = opName.indexOf("$"); opName = opName.substring($index + 1, opName.length); + return "<" + opName + "@" + this.tokens.get(this.index) + ":\"" + this.text + "\">"; } } class InsertBeforeOp extends RewriteOperation { - /** - * @param {CommonTokenStream} tokens - * @param {number} index - * @param {number} instructionIndex - * @param {Text} text - */ - constructor(tokens, index, instructionIndex, text) { + public constructor(tokens: TokenStream, index: number, instructionIndex: number, text: unknown) { super(tokens, index, instructionIndex, text); } /** - * @param {string[]} buf - * @returns {number} the index of the next token to operate on + * @returns the index of the next token to operate on */ - execute(buf) { + public override execute(buf: string[]): number { if (this.text) { buf.push(this.text.toString()); } @@ -397,51 +454,42 @@ class InsertBeforeOp extends RewriteOperation { if (this.tokens.get(this.index).type !== Token.EOF) { buf.push(String(this.tokens.get(this.index).text)); } + return this.index + 1; } } class InsertAfterOp extends InsertBeforeOp { - /** - * @param {CommonTokenStream} tokens - * @param {number} index - * @param {number} instructionIndex - * @param {Text} text - */ - constructor(tokens, index, instructionIndex, text) { + public constructor(tokens: TokenStream, index: number, instructionIndex: number, text?: string | null) { super(tokens, index + 1, instructionIndex, text); // insert after is insert before index+1 } } class ReplaceOp extends RewriteOperation { - /** - * @param {CommonTokenStream} tokens - * @param {number} from - * @param {number} to - * @param {number} instructionIndex - * @param {Text} text - */ - constructor(tokens, from, to, instructionIndex, text) { + public lastIndex: number; + + public constructor(tokens: TokenStream, from: number, to: number, instructionIndex: number, text?: string | null) { super(tokens, from, instructionIndex, text); this.lastIndex = to; } /** - * @param {string[]} buf - * @returns {number} the index of the next token to operate on + * @returns the index of the next token to operate on */ - execute(buf) { + public override execute(buf: string[]): number { if (this.text) { buf.push(this.text.toString()); } + return this.lastIndex + 1; } - toString() { + public override toString(): string { if (this.text == null) { return ""; } + return ""; } diff --git a/src/TraceListener.js b/src/TraceListener.js deleted file mode 100644 index aa2a9fc..0000000 --- a/src/TraceListener.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTreeListener } from "./tree/ParseTreeListener.js"; - -export class TraceListener extends ParseTreeListener { - constructor(parser) { - super(); - this.parser = parser; - } - - enterEveryRule(ctx) { - console.log("enter " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + this.parser._input.LT(1).text); - } - - visitTerminal(node) { - console.log("consume " + node.symbol + " rule " + this.parser.ruleNames[this.parser._ctx.ruleIndex]); - } - - exitEveryRule(ctx) { - console.log("exit " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + this.parser._input.LT(1).text); - } -} diff --git a/src/TraceListener.ts b/src/TraceListener.ts new file mode 100644 index 0000000..6626670 --- /dev/null +++ b/src/TraceListener.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable no-underscore-dangle */ + +import { Parser } from "./Parser.js"; +import { ParserRuleContext } from "./ParserRuleContext.js"; +import { ParseTreeListener } from "./tree/ParseTreeListener.js"; +import { TerminalNode } from "./tree/TerminalNode.js"; + +export class TraceListener implements ParseTreeListener { + private parser: Parser; + + public constructor(parser: Parser) { + this.parser = parser; + } + + public enterEveryRule(ctx: ParserRuleContext): void { + console.log("enter " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + + this.parser.inputStream.LT(1)!.text); + } + + public visitTerminal(node: TerminalNode): void { + console.log("consume " + node.getSymbol() + " rule " + this.parser.ruleNames[this.parser.context!.ruleIndex]); + } + + public exitEveryRule(ctx: ParserRuleContext): void { + console.log("exit " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + + this.parser.inputStream.LT(1)!.text); + } + + public visitErrorNode(_node: TerminalNode): void { + // intentionally empty + } +} diff --git a/src/Vocabulary.d.ts b/src/Vocabulary.d.ts deleted file mode 100644 index d795b31..0000000 --- a/src/Vocabulary.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * This class provides a default implementation of the {@link Vocabulary} - * interface. - * - * @author Sam Harwell - */ -export declare class Vocabulary { - /* eslint-disable @typescript-eslint/naming-convention */ - - /** - * Gets an empty {@link Vocabulary} instance. - * - *

            - * No literal or symbol names are assigned to token types, so - * {@link #getDisplayName(int)} returns the numeric value for all tokens - * except {@link Token#EOF}.

            - */ - public static EMPTY_VOCABULARY: Vocabulary; - - private static readonly EMPTY_NAMES: string[]; - - /* eslint-enable @typescript-eslint/naming-convention */ - - public readonly maxTokenType: number; - - private readonly literalNames: Array; - private readonly symbolicNames: Array; - private readonly displayNames: Array; - - /** - * Constructs a new instance of {@link Vocabulary} from the specified - * literal, symbolic, and display token names. - * - * @param literalNames The literal names assigned to tokens, or {@code null} - * if no literal names are assigned. - * @param symbolicNames The symbolic names assigned to tokens, or - * {@code null} if no symbolic names are assigned. - * @param displayNames The display names assigned to tokens, or {@code null} - * to use the values in {@code literalNames} and {@code symbolicNames} as - * the source of display names, as described in - * {@link #getDisplayName(int)}. - * - * @see #getLiteralName(int) - * @see #getSymbolicName(int) - * @see #getDisplayName(int) - */ - public constructor( - literalNames: Array, - symbolicNames: Array, - displayNames?: Array | null - ); - - /** - * Returns a {@link Vocabulary} instance from the specified set of token - * names. This method acts as a compatibility layer for the single - * {@code tokenNames} array generated by previous releases of ANTLR. - * - *

            The resulting vocabulary instance returns {@code null} for - * {@link #getLiteralName(int)} and {@link #getSymbolicName(int)}, and the - * value from {@code tokenNames} for the display names.

            - * - * @param tokenNames The token names, or {@code null} if no token names are - * available. - * @returns A {@link Vocabulary} instance which uses {@code tokenNames} for - * the display names of tokens. - */ - public static fromTokenNames(tokenNames: Array): Vocabulary; - - public getMaxTokenType(): number; - public getLiteralName(tokenType: number): string | null; - public getSymbolicName(tokenType: number): string | null; - public getDisplayName(tokenType: number): string | null; - public getLiteralNames(): Array; - public getSymbolicNames(): Array; - public getDisplayNames(): Array; -} diff --git a/src/Vocabulary.js b/src/Vocabulary.js deleted file mode 100644 index f626551..0000000 --- a/src/Vocabulary.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Token } from "./Token.js"; - -export class Vocabulary { - constructor(literalNames, symbolicNames, displayNames) { - this.literalNames = literalNames != null ? literalNames : Vocabulary.EMPTY_NAMES; - this.symbolicNames = symbolicNames != null ? symbolicNames : Vocabulary.EMPTY_NAMES; - this.displayNames = displayNames != null ? displayNames : Vocabulary.EMPTY_NAMES; - // See note here on -1 part: https://github.com/antlr/antlr4/pull/1146 - this.maxTokenType = - Math.max(this.displayNames.length, - Math.max(this.literalNames.length, this.symbolicNames.length)) - 1; - } - - static fromTokenNames(tokenNames) { - if (tokenNames == null || tokenNames.length === 0) { - return Vocabulary.EMPTY_VOCABULARY; - } - - const literalNames = [...tokenNames]; - const symbolicNames = [...tokenNames]; - for (let i = 0; i < tokenNames.length; i++) { - let tokenName = tokenNames[i]; - if (tokenName == null) { - continue; - } - - if (!tokenName.isEmpty()) { - const firstChar = tokenName.charAt(0); - if (firstChar === "'") { - symbolicNames[i] = null; - continue; - } else if (firstChar.toUpperCase() === firstChar) { - literalNames[i] = null; - continue; - } - } - - // wasn't a literal or symbolic name - literalNames[i] = null; - symbolicNames[i] = null; - } - - return new Vocabulary(literalNames, symbolicNames, tokenNames); - } - - getMaxTokenType() { - return this.maxTokenType; - } - - getLiteralName(tokenType) { - if (tokenType >= 0 && tokenType < this.literalNames.length) { - return this.literalNames[tokenType]; - } - - return null; - } - - getSymbolicName(tokenType) { - if (tokenType >= 0 && tokenType < this.symbolicNames.length) { - return this.symbolicNames[tokenType]; - } - - if (tokenType === Token.EOF) { - return "EOF"; - } - - return null; - } - - getDisplayName(tokenType) { - if (tokenType >= 0 && tokenType < this.displayNames.length) { - const displayName = this.displayNames[tokenType]; - if (displayName != null) { - return displayName; - } - } - - const literalName = this.getLiteralName(tokenType); - if (literalName != null) { - return literalName; - } - - const symbolicName = this.getSymbolicName(tokenType); - if (symbolicName != null) { - return symbolicName; - } - - return `${tokenType}`; - } - - getLiteralNames() { - return this.literalNames; - } - - getSymbolicNames() { - return this.symbolicNames; - } - - getDisplayNames() { - return this.displayNames; - } -} - -Vocabulary.EMPTY_NAMES = []; -Vocabulary.EMPTY_VOCABULARY = new Vocabulary(Vocabulary.EMPTY_NAMES, Vocabulary.EMPTY_NAMES, Vocabulary.EMPTY_NAMES); diff --git a/src/Vocabulary.ts b/src/Vocabulary.ts new file mode 100644 index 0000000..9b74f08 --- /dev/null +++ b/src/Vocabulary.ts @@ -0,0 +1,158 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import { Token } from "./Token.js"; + +export class Vocabulary { + public static readonly EMPTY_NAMES: string[] = []; + + /** + * Gets an empty {@link Vocabulary} instance. + * + *

            + * No literal or symbol names are assigned to token types, so + * {@link #getDisplayName(int)} returns the numeric value for all tokens + * except {@link Token#EOF}.

            + */ + public static readonly EMPTY_VOCABULARY = + new Vocabulary(Vocabulary.EMPTY_NAMES, Vocabulary.EMPTY_NAMES, Vocabulary.EMPTY_NAMES); + + public readonly maxTokenType: number; + + private readonly literalNames: Array; + private readonly symbolicNames: Array; + private readonly displayNames: Array; + + /** + * Constructs a new instance of {@link Vocabulary} from the specified + * literal, symbolic, and display token names. + * + * @param literalNames The literal names assigned to tokens, or {@code null} + * if no literal names are assigned. + * @param symbolicNames The symbolic names assigned to tokens, or + * {@code null} if no symbolic names are assigned. + * @param displayNames The display names assigned to tokens, or {@code null} + * to use the values in {@code literalNames} and {@code symbolicNames} as + * the source of display names, as described in + * {@link #getDisplayName(int)}. + */ + public constructor(literalNames: Array, symbolicNames: Array, + displayNames?: Array | null, + ) { + this.literalNames = literalNames != null ? literalNames : Vocabulary.EMPTY_NAMES; + this.symbolicNames = symbolicNames != null ? symbolicNames : Vocabulary.EMPTY_NAMES; + this.displayNames = displayNames != null ? displayNames : Vocabulary.EMPTY_NAMES; + + // See note here on -1 part: https://github.com/antlr/antlr4/pull/1146 + this.maxTokenType = Math.max(this.displayNames.length, Math.max(this.literalNames.length, + this.symbolicNames.length)) - 1; + } + + /** + * Returns a {@link Vocabulary} instance from the specified set of token + * names. This method acts as a compatibility layer for the single + * {@code tokenNames} array generated by previous releases of ANTLR. + * + *

            The resulting vocabulary instance returns {@code null} for + * {@link #getLiteralName(int)} and {@link #getSymbolicName(int)}, and the + * value from {@code tokenNames} for the display names.

            + * + * @param tokenNames The token names, or {@code null} if no token names are + * available. + * @returns A {@link Vocabulary} instance which uses {@code tokenNames} for + * the display names of tokens. + */ + public static fromTokenNames(tokenNames: Array | null): Vocabulary { + if (tokenNames == null || tokenNames.length === 0) { + return Vocabulary.EMPTY_VOCABULARY; + } + + const literalNames = [...tokenNames]; + const symbolicNames = [...tokenNames]; + for (let i = 0; i < tokenNames.length; i++) { + const tokenName = tokenNames[i]; + if (tokenName == null) { + continue; + } + + if (tokenName?.length > 0) { + const firstChar = tokenName.charAt(0); + if (firstChar === "'") { + symbolicNames[i] = null; + continue; + } else if (firstChar.toUpperCase() === firstChar) { + literalNames[i] = null; + continue; + } + } + + // wasn't a literal or symbolic name + literalNames[i] = null; + symbolicNames[i] = null; + } + + return new Vocabulary(literalNames, symbolicNames, tokenNames); + } + + public getMaxTokenType(): number { + return this.maxTokenType; + } + + public getLiteralName(tokenType: number): string | null { + if (tokenType >= 0 && tokenType < this.literalNames.length) { + return this.literalNames[tokenType]; + } + + return null; + } + + public getSymbolicName(tokenType: number): string | null { + if (tokenType >= 0 && tokenType < this.symbolicNames.length) { + return this.symbolicNames[tokenType]; + } + + if (tokenType === Token.EOF) { + return "EOF"; + } + + return null; + } + + public getDisplayName(tokenType: number): string | null { + if (tokenType >= 0 && tokenType < this.displayNames.length) { + const displayName = this.displayNames[tokenType]; + if (displayName != null) { + return displayName; + } + } + + const literalName = this.getLiteralName(tokenType); + if (literalName != null) { + return literalName; + } + + const symbolicName = this.getSymbolicName(tokenType); + if (symbolicName != null) { + return symbolicName; + } + + return `${tokenType}`; + } + + public getLiteralNames(): Array { + return this.literalNames; + } + + public getSymbolicNames(): Array { + return this.symbolicNames; + } + + public getDisplayNames(): Array { + return this.displayNames; + } +} diff --git a/src/WritableToken.ts b/src/WritableToken.ts new file mode 100644 index 0000000..284b15b --- /dev/null +++ b/src/WritableToken.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Token } from "./Token.js"; + +/** + * This interface provides access to all of the information about a token by + * exposing the properties of the token as getter methods. + */ +export interface WritableToken extends Token { + setText(text: string | null): void; + + setType(ttype: number): void; + + setLine(line: number): void; + + setCharPositionInLine(pos: number): void; + + setChannel(channel: number): void; + + setTokenIndex(index: number): void; + + toString(): string; +} + +export const isWritableToken = (candidate: unknown): candidate is WritableToken => { + return (candidate as WritableToken).setText !== undefined; +}; diff --git a/src/atn/ATN.d.ts b/src/atn/ATN.d.ts deleted file mode 100644 index bdc136c..0000000 --- a/src/atn/ATN.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntervalSet } from "../misc/IntervalSet.js"; -import { RuleContext } from "./RuleContext.js"; -import { ATNState } from "./ATNState.js"; -import { DecisionState } from "./DecisionState.js"; -import { RuleStartState } from "./RuleStartState.js"; -import { RuleStopState } from "./RuleStopState.js"; -import { LexerAction } from "./LexerAction.js"; - -export declare class ATN { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static INVALID_ALT_NUMBER: number; - - public readonly grammarType: number; - public readonly maxTokenType: number; - - public readonly states: ATNState[]; - public readonly decisionToState: DecisionState[]; - public readonly ruleToStartState: RuleStartState[]; - public readonly ruleToStopState: RuleStopState[]; - public readonly modeNameToStartState: { [name: string]: RuleStartState; }; - public readonly ruleToTokenType: number[]; - public readonly lexerActions: LexerAction[]; - public readonly modeToStartState: RuleStartState[]; - - public getExpectedTokens(stateNumber: number, ctx: RuleContext): IntervalSet; - public nextTokens(atnState: ATNState, ctx?: RuleContext): IntervalSet; -} diff --git a/src/atn/ATN.js b/src/atn/ATN.js deleted file mode 100644 index 17cfffa..0000000 --- a/src/atn/ATN.js +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { LL1Analyzer } from './LL1Analyzer.js'; -import { IntervalSet } from '../misc/IntervalSet.js'; -import { Token } from '../Token.js'; - -export class ATN { - constructor(grammarType, maxTokenType) { - /** - * Used for runtime deserialization of ATNs from strings - * The type of the ATN. - */ - this.grammarType = grammarType; - // The maximum value for any symbol recognized by a transition in the ATN. - this.maxTokenType = maxTokenType; - this.states = []; - /** - * Each subrule/rule is a decision point and we must track them so we - * can go back later and build DFA predictors for them. This includes - * all the rules, subrules, optional blocks, ()+, ()* etc... - */ - this.decisionToState = []; - // Maps from rule index to starting state number. - this.ruleToStartState = []; - // Maps from rule index to stop state number. - this.ruleToStopState = []; - this.modeNameToStartState = {}; - /** - * For lexer ATNs, this maps the rule index to the resulting token type. - * For parser ATNs, this maps the rule index to the generated bypass token - * type if the {@link ATNDeserializationOptions//isGenerateRuleBypassTransitions} - * deserialization option was specified; otherwise, this is {@code null} - */ - this.ruleToTokenType = []; - /** - * For lexer ATNs, this is an array of {@link LexerAction} objects which may - * be referenced by action transitions in the ATN - */ - this.lexerActions = []; - this.modeToStartState = []; - } - - /** - * Compute the set of valid tokens that can occur starting in state {@code s}. - * If {@code ctx} is null, the set of tokens will not include what can follow - * the rule surrounding {@code s}. In other words, the set will be - * restricted to tokens reachable staying within {@code s}'s rule - */ - nextTokensInContext(s, ctx) { - const anal = new LL1Analyzer(this); - return anal.LOOK(s, null, ctx); - } - - /** - * Compute the set of valid tokens that can occur starting in {@code s} and - * staying in same rule. {@link Token//EPSILON} is in set if we reach end of - * rule - */ - nextTokensNoContext(s) { - if (s.nextTokenWithinRule !== null) { - return s.nextTokenWithinRule; - } - s.nextTokenWithinRule = this.nextTokensInContext(s, null); - s.nextTokenWithinRule.readOnly = true; - return s.nextTokenWithinRule; - } - - nextTokens(s, ctx) { - if (ctx === undefined) { - return this.nextTokensNoContext(s); - } else { - return this.nextTokensInContext(s, ctx); - } - } - - addState(state) { - if (state !== null) { - state.atn = this; - state.stateNumber = this.states.length; - } - this.states.push(state); - } - - removeState(state) { - this.states[state.stateNumber] = null; // just free mem, don't shift states in list - } - - defineDecisionState(s) { - this.decisionToState.push(s); - s.decision = this.decisionToState.length - 1; - return s.decision; - } - - getDecisionState(decision) { - if (this.decisionToState.length === 0) { - return null; - } else { - return this.decisionToState[decision]; - } - } - - /** - * Computes the set of input symbols which could follow ATN state number - * {@code stateNumber} in the specified full {@code context}. This method - * considers the complete parser context, but does not evaluate semantic - * predicates (i.e. all predicates encountered during the calculation are - * assumed true). If a path in the ATN exists from the starting state to the - * {@link RuleStopState} of the outermost context without matching any - * symbols, {@link Token//EOF} is added to the returned set. - * - *

            If {@code context} is {@code null}, it is treated as - * {@link ParserRuleContext//EMPTY}.

            - * - * @param stateNumber the ATN state number - * @param ctx the full parse context - * - * @return {IntervalSet} The set of potentially valid input symbols which could follow the - * specified state in the specified context. - * - * @throws IllegalArgumentException if the ATN does not contain a state with - * number {@code stateNumber} - */ - getExpectedTokens(stateNumber, ctx) { - if (stateNumber < 0 || stateNumber >= this.states.length) { - throw ("Invalid state number."); - } - - const s = this.states[stateNumber]; - let following = this.nextTokens(s); - if (!following.contains(Token.EPSILON)) { - return following; - } - - const expected = new IntervalSet(); - expected.addSet(following); - expected.removeOne(Token.EPSILON); - while (ctx !== null && ctx.invokingState >= 0 && following.contains(Token.EPSILON)) { - const invokingState = this.states[ctx.invokingState]; - const rt = invokingState.transitions[0]; - following = this.nextTokens(rt.followState); - expected.addSet(following); - expected.removeOne(Token.EPSILON); - ctx = ctx.parent; - } - - if (following.contains(Token.EPSILON)) { - expected.addOne(Token.EOF); - } - - return expected; - } -} - -ATN.INVALID_ALT_NUMBER = 0; diff --git a/src/atn/ATN.ts b/src/atn/ATN.ts new file mode 100644 index 0000000..d870799 --- /dev/null +++ b/src/atn/ATN.ts @@ -0,0 +1,180 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { LL1Analyzer } from "./LL1Analyzer.js"; +import { IntervalSet } from "../misc/IntervalSet.js"; +import { RuleContext } from "../RuleContext.js"; +import { ATNState } from "./ATNState.js"; +import { DecisionState } from "./DecisionState.js"; +import { RuleStartState } from "./RuleStartState.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { LexerAction } from "./LexerAction.js"; +import { TokensStartState } from "./TokensStartState.js"; +import { Token } from "../Token.js"; +import { RuleTransition } from "./RuleTransition.js"; + +export class ATN { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static INVALID_ALT_NUMBER = 0; + + /** + * Used for runtime deserialization of ATNs from strings + * The type of the ATN. + */ + public readonly grammarType: number; + + /** The maximum value for any symbol recognized by a transition in the ATN. */ + public readonly maxTokenType: number; + + public readonly states: Array = []; + + /** + * Each subrule/rule is a decision point and we must track them so we + * can go back later and build DFA predictors for them. This includes + * all the rules, subrules, optional blocks, ()+, ()* etc... + */ + public readonly decisionToState: DecisionState[] = []; + + /** Maps from rule index to starting state number. */ + public ruleToStartState: Array = []; // Initialized by the ATN deserializer. + + /** Maps from rule index to stop state number. */ + public ruleToStopState: Array = []; // Initialized by the ATN deserializer. + + public readonly modeNameToStartState = new Map(); + + /** + * For lexer ATNs, this maps the rule index to the resulting token type. + * For parser ATNs, this maps the rule index to the generated bypass token + * type if the {@link ATNDeserializationOptions//isGenerateRuleBypassTransitions} + * deserialization option was specified; otherwise, this is {@code null} + */ + public ruleToTokenType: number[] = []; // Initialized by the ATN deserializer. + + /** + * For lexer ATNs, this is an array of {@link LexerAction} objects which may + * be referenced by action transitions in the ATN + */ + public lexerActions: LexerAction[] = []; + + public readonly modeToStartState: Array = []; + + public constructor(grammarType: number, maxTokenType: number) { + this.grammarType = grammarType; + this.maxTokenType = maxTokenType; + } + + public addState(state: ATNState | null): void { + if (state !== null) { + state.atn = this; + state.stateNumber = this.states.length; + } + this.states.push(state); + } + + public removeState(state: ATNState): void { + this.states[state.stateNumber] = null; // just free mem, don't shift states in list + } + + public defineDecisionState(s: DecisionState): number { + this.decisionToState.push(s); + s.decision = this.decisionToState.length - 1; + + return s.decision; + } + + public getDecisionState(decision: number): DecisionState | null { + if (this.decisionToState.length === 0) { + return null; + } else { + return this.decisionToState[decision]; + } + } + + /** + * Computes the set of input symbols which could follow ATN state number + * {@code stateNumber} in the specified full {@code context}. This method + * considers the complete parser context, but does not evaluate semantic + * predicates (i.e. all predicates encountered during the calculation are + * assumed true). If a path in the ATN exists from the starting state to the + * {@link RuleStopState} of the outermost context without matching any + * symbols, {@link Token//EOF} is added to the returned set. + * + *

            If {@code context} is {@code null}, it is treated as + * {@link ParserRuleContext//EMPTY}.

            + * + * @param stateNumber the ATN state number + * @param context the full parse context + * + * @returns {IntervalSet} The set of potentially valid input symbols which could follow the + * specified state in the specified context. + * + * @throws IllegalArgumentException if the ATN does not contain a state with + * number {@code stateNumber} + */ + public getExpectedTokens(stateNumber: number, context: RuleContext): IntervalSet { + if (stateNumber < 0 || stateNumber >= this.states.length) { + throw new Error("Invalid state number."); + } + + const s = this.states[stateNumber]!; + let following = this.nextTokens(s); + if (!following.contains(Token.EPSILON)) { + return following; + } + + let ctx: RuleContext | null = context; + const expected = new IntervalSet(); + expected.addSet(following); + expected.removeOne(Token.EPSILON); + while (ctx !== null && ctx.invokingState >= 0 && following.contains(Token.EPSILON)) { + const invokingState = this.states[ctx.invokingState]!; + const rt = invokingState.transitions[0] as RuleTransition; + following = this.nextTokens(rt.followState); + expected.addSet(following); + expected.removeOne(Token.EPSILON); + ctx = ctx.parent; + } + + if (following.contains(Token.EPSILON)) { + expected.addOne(Token.EOF); + } + + return expected; + } + + /** + * Compute the set of valid tokens that can occur starting in state {@code s}. + * If {@code ctx} is null, the set of tokens will not include what can follow + * the rule surrounding {@code s}. In other words, the set will be + * restricted to tokens reachable staying within {@code s}'s rule. + * + * @param atnState tbd + * @param ctx tbd + * + * @returns tbd + */ + public nextTokens(atnState: ATNState): IntervalSet; + public nextTokens(atnState: ATNState, ctx: RuleContext | null): IntervalSet; + public nextTokens(atnState: ATNState, ctx?: RuleContext | null): IntervalSet { + if (ctx === undefined) { + if (atnState.nextTokenWithinRule !== null) { + return atnState.nextTokenWithinRule; + } + + atnState.nextTokenWithinRule = this.nextTokens(atnState, null); + atnState.nextTokenWithinRule.setReadonly(true); + + return atnState.nextTokenWithinRule; + + } + + const analyzer = new LL1Analyzer(this); + + return analyzer.LOOK(atnState, null, ctx); + } + +} diff --git a/src/atn/ATNConfig.d.ts b/src/atn/ATNConfig.d.ts deleted file mode 100644 index 5f33939..0000000 --- a/src/atn/ATNConfig.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; - -export declare class ATNConfig { - public state: ATNState; -} diff --git a/src/atn/ATNConfig.js b/src/atn/ATNConfig.js deleted file mode 100644 index 39c7d3b..0000000 --- a/src/atn/ATNConfig.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { SemanticContext } from './SemanticContext.js'; -import { HashCode } from "../misc/HashCode.js"; - -function checkParams(params, isCfg) { - if (params === null) { - const result = { state: null, alt: null, context: null, semanticContext: null }; - if (isCfg) { - result.reachesIntoOuterContext = 0; - } - return result; - } else { - const props = {}; - props.state = params.state || null; - props.alt = (params.alt === undefined) ? null : params.alt; - props.context = params.context || null; - props.semanticContext = params.semanticContext || null; - if (isCfg) { - props.reachesIntoOuterContext = params.reachesIntoOuterContext || 0; - props.precedenceFilterSuppressed = params.precedenceFilterSuppressed || false; - } - return props; - } -} - -export class ATNConfig { - /** - * @param {Object} params A tuple: (ATN state, predicted alt, syntactic, semantic context). - * The syntactic context is a graph-structured stack node whose - * path(s) to the root is the rule invocation(s) - * chain used to arrive at the state. The semantic context is - * the tree of semantic predicates encountered before reaching - * an ATN state - */ - constructor(params, config) { - this.checkContext(params, config); - params = checkParams(params); - config = checkParams(config, true); - // The ATN state associated with this configuration/// - this.state = params.state !== null ? params.state : config.state; - // What alt (or lexer rule) is predicted by this configuration/// - this.alt = params.alt !== null ? params.alt : config.alt; - /** - * The stack of invoking states leading to the rule/states associated - * with this config. We track only those contexts pushed during - * execution of the ATN simulator - */ - this.context = params.context !== null ? params.context : config.context; - this.semanticContext = params.semanticContext !== null ? params.semanticContext : - (config.semanticContext !== null ? config.semanticContext : SemanticContext.NONE); - // TODO: make it a boolean then - /** - * We cannot execute predicates dependent upon local context unless - * we know for sure we are in the correct context. Because there is - * no way to do this efficiently, we simply cannot evaluate - * dependent predicates unless we are in the rule that initially - * invokes the ATN simulator. - * closure() tracks the depth of how far we dip into the - * outer context: depth > 0. Note that it may not be totally - * accurate depth since I don't ever decrement - */ - this.reachesIntoOuterContext = config.reachesIntoOuterContext; - this.precedenceFilterSuppressed = config.precedenceFilterSuppressed; - } - - checkContext(params, config) { - if ((params.context === null || params.context === undefined) && - (config === null || config.context === null || config.context === undefined)) { - this.context = null; - } - } - - hashCode() { - const hash = new HashCode(); - this.updateHashCode(hash); - return hash.finish(); - } - - updateHashCode(hash) { - hash.update(this.state.stateNumber, this.alt, this.context, this.semanticContext); - } - - /** - * An ATN configuration is equal to another if both have - * the same state, they predict the same alternative, and - * syntactic/semantic contexts are the same - */ - equals(other) { - if (this === other) { - return true; - } else if (!(other instanceof ATNConfig)) { - return false; - } else { - return this.state.stateNumber === other.state.stateNumber && - this.alt === other.alt && - (this.context === null ? other.context === null : this.context.equals(other.context)) && - this.semanticContext.equals(other.semanticContext) && - this.precedenceFilterSuppressed === other.precedenceFilterSuppressed; - } - } - - hashCodeForConfigSet() { - const hash = new HashCode(); - hash.update(this.state.stateNumber, this.alt, this.semanticContext); - return hash.finish(); - } - - equalsForConfigSet(other) { - if (this === other) { - return true; - } else if (!(other instanceof ATNConfig)) { - return false; - } else { - return this.state.stateNumber === other.state.stateNumber && - this.alt === other.alt && - this.semanticContext.equals(other.semanticContext); - } - } - - toString() { - return "(" + this.state + "," + this.alt + - (this.context !== null ? ",[" + this.context.toString() + "]" : "") + - (this.semanticContext !== SemanticContext.NONE ? - ("," + this.semanticContext.toString()) - : "") + - (this.reachesIntoOuterContext > 0 ? - (",up=" + this.reachesIntoOuterContext) - : "") + ")"; - } -} diff --git a/src/atn/ATNConfig.ts b/src/atn/ATNConfig.ts new file mode 100644 index 0000000..8d7b9c8 --- /dev/null +++ b/src/atn/ATNConfig.ts @@ -0,0 +1,201 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable jsdoc/require-param, jsdoc/require-returns */ + +import { SemanticContext } from "./SemanticContext.js"; +import { HashCode } from "../misc/HashCode.js"; +import { ATNState } from "./ATNState.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { Recognizer } from "../Recognizer.js"; +import { ATNSimulator } from "./ATNSimulator.js"; + +export interface IATNConfigParameters { + state?: ATNState | null, + alt?: number | null, + context?: PredictionContext | null, + semanticContext?: SemanticContext | null, + reachesIntoOuterContext?: number | null, + precedenceFilterSuppressed?: number, +}; + +export interface ICheckedConfigParameters { + state: ATNState | null, + alt: number | null, + context: PredictionContext | null, + semanticContext: SemanticContext | null, + reachesIntoOuterContext: number | null, + precedenceFilterSuppressed?: boolean, +}; + +const checkParams = (params: IATNConfigParameters | null): ICheckedConfigParameters => { + if (params === null) { + return { + state: null, + alt: null, + context: null, + semanticContext: null, + reachesIntoOuterContext: null, + }; + } else { + return { + state: params.state ?? null, + alt: params.alt ?? null, + context: params.context ?? null, + semanticContext: params.semanticContext ?? null, + reachesIntoOuterContext: null, + }; + } +}; + +const checkConfig = (params: ATNConfig | null): ICheckedConfigParameters => { + if (params === null) { + return { + state: null, + alt: null, + context: null, + semanticContext: null, + reachesIntoOuterContext: 0, + }; + } else { + const props = { + state: params.state ?? null, + alt: params.alt ?? null, + context: params.context ?? null, + semanticContext: params.semanticContext ?? null, + reachesIntoOuterContext: params.reachesIntoOuterContext ?? 0, + precedenceFilterSuppressed: params.precedenceFilterSuppressed ?? false, + }; + + return props; + } +}; + +export class ATNConfig { + /** The ATN state associated with this configuration */ + public readonly state: ATNState; + + /** What alt (or lexer rule) is predicted by this configuration */ + public readonly alt: number; + + /** + * The stack of invoking states leading to the rule/states associated + * with this config. We track only those contexts pushed during + * execution of the ATN simulator. + */ + public context: PredictionContext | null; + + /** + * We cannot execute predicates dependent upon local context unless + * we know for sure we are in the correct context. Because there is + * no way to do this efficiently, we simply cannot evaluate + * dependent predicates unless we are in the rule that initially + * invokes the ATN simulator. + * + * closure() tracks the depth of how far we dip into the outer context: + * depth > 0. Note that it may not be totally accurate depth since I + * don't ever decrement. TODO: make it a boolean then

            + */ + public reachesIntoOuterContext: number; + + public precedenceFilterSuppressed = false; + + public readonly semanticContext: SemanticContext; + + /** + * @param {object} params A tuple: (ATN state, predicted alt, syntactic, semantic context). + * The syntactic context is a graph-structured stack node whose + * path(s) to the root is the rule invocation(s) + * chain used to arrive at the state. The semantic context is + * the tree of semantic predicates encountered before reaching + * an ATN state + */ + public constructor(params: IATNConfigParameters, config: ATNConfig | null) { + this.checkContext(params, config); + const checkedParams = checkParams(params); + const checkedConfig = checkConfig(config); + + this.state = checkedParams.state ?? checkedConfig.state!; + this.alt = checkedParams.alt ?? checkedConfig.alt ?? 0; + this.context = checkedParams.context ?? checkedConfig.context; + this.semanticContext = checkedParams.semanticContext ?? (checkedConfig.semanticContext ?? SemanticContext.NONE); + this.reachesIntoOuterContext = checkedConfig.reachesIntoOuterContext ?? 0; + this.precedenceFilterSuppressed = checkedConfig.precedenceFilterSuppressed ?? false; + } + + public hashCode(): number { + const hash = new HashCode(); + this.updateHashCode(hash); + + return hash.finish(); + } + + public updateHashCode(hash: HashCode): void { + hash.update(this.state.stateNumber, this.alt, this.context, this.semanticContext); + } + + /** + * An ATN configuration is equal to another if both have + * the same state, they predict the same alternative, and + * syntactic/semantic contexts are the same + */ + public equals(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof ATNConfig)) { + return false; + } else { + return (this.state.stateNumber === other.state.stateNumber) && + (this.alt === other.alt) && + (this.context === null ? other.context === null : this.context.equals(other.context)) && + this.semanticContext.equals(other.semanticContext) && + this.precedenceFilterSuppressed === other.precedenceFilterSuppressed; + } + } + + public hashCodeForConfigSet(): number { + const hash = new HashCode(); + hash.update(this.state.stateNumber, this.alt, this.semanticContext); + + return hash.finish(); + } + + public equalsForConfigSet(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof ATNConfig)) { + return false; + } else { + return this.state.stateNumber === other.state.stateNumber && + this.alt === other.alt && + this.semanticContext.equals(other.semanticContext); + } + } + + public toString(_recog?: Recognizer | null, showAlt = true): string { + let alt = ""; + if (showAlt) { + alt = "," + this.alt; + } + + return "(" + this.state + alt + + (this.context !== null ? ",[" + this.context.toString() + "]" : "") + + (this.semanticContext !== SemanticContext.NONE ? + ("," + this.semanticContext.toString()) + : "") + + (this.reachesIntoOuterContext > 0 ? + (",up=" + this.reachesIntoOuterContext) + : "") + ")"; + } + + private checkContext(params: IATNConfigParameters, config: ATNConfig | null) { + if ((params.context === null || params.context === undefined) && + (config === null || config.context === null || config.context === undefined)) { + this.context = null; + } + } + +} diff --git a/src/atn/ATNConfigSet.d.ts b/src/atn/ATNConfigSet.d.ts deleted file mode 100644 index 6a5e5ce..0000000 --- a/src/atn/ATNConfigSet.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNConfig } from "./ATNConfig.js"; - -export declare class ATNConfigSet { - public configs: ATNConfig[]; -} diff --git a/src/atn/ATNConfigSet.js b/src/atn/ATNConfigSet.js deleted file mode 100644 index f3bb913..0000000 --- a/src/atn/ATNConfigSet.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATN } from './ATN.js'; -import { SemanticContext } from './SemanticContext.js'; -import { merge } from './PredictionContextUtils.js'; -import { arrayToString } from "../utils/arrayToString.js"; -import { HashSet } from "../misc/HashSet.js"; -import { equalArrays } from "../utils/equalArrays.js"; -import { HashCode } from "../misc/HashCode.js"; - -function hashATNConfig(c) { - return c.hashCodeForConfigSet(); -} - -function equalATNConfigs(a, b) { - if (a === b) { - return true; - } else if (a === null || b === null) { - return false; - } else - return a.equalsForConfigSet(b); -} - -/** - * Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track - * info about the set, with support for combining similar configurations using a - * graph-structured stack - */ -export class ATNConfigSet { - constructor(fullCtx) { - /** - * 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 - */ - this.configLookup = new HashSet(hashATNConfig, equalATNConfigs); - /** - * Indicates that this configuration set is part of a full context - * 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 - */ - this.fullCtx = fullCtx === undefined ? true : fullCtx; - /** - * Indicates that the set of configurations is read-only. Do not - * allow any code to manipulate the set; DFA states will point at - * the sets and they must not change. This does not protect the other - * fields; in particular, conflictingAlts is set after - * we've made this readonly - */ - this.readOnly = false; - // Track the elements as they are added to the set; supports get(i)/// - this.configs = []; - - // TODO: these fields make me pretty uncomfortable but nice to pack up info - // together, saves recomputation - // TODO: can we track conflicts as they are added to save scanning configs - // later? - this.uniqueAlt = 0; - this.conflictingAlts = null; - - /** - * 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 - */ - this.hasSemanticContext = false; - this.dipsIntoOuterContext = false; - - this.cachedHashCode = -1; - } - - /** - * Adding a new config means merging contexts with existing configs for - * {@code (s, i, pi, _)}, where {@code s} is the - * {@link ATNConfig//state}, {@code i} is the {@link ATNConfig//alt}, and - * {@code pi} is the {@link ATNConfig//semanticContext}. We use - * {@code (s,i,pi)} as key. - * - *

            This method updates {@link //dipsIntoOuterContext} and - * {@link //hasSemanticContext} when necessary.

            - */ - add(config, mergeCache) { - if (mergeCache === undefined) { - mergeCache = null; - } - if (this.readOnly) { - throw "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); - 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; - } - - getStates() { - const states = new HashSet(); - for (let i = 0; i < this.configs.length; i++) { - states.add(this.configs[i].state); - } - return states; - } - - getPredicates() { - const preds = []; - for (let i = 0; i < this.configs.length; i++) { - const c = this.configs[i].semanticContext; - if (c !== SemanticContext.NONE) { - preds.push(c.semanticContext); - } - } - return preds; - } - - optimizeConfigs(interpreter) { - if (this.readOnly) { - throw "This set is readonly"; - } - if (this.configLookup.length === 0) { - return; - } - for (let i = 0; i < this.configs.length; i++) { - const config = this.configs[i]; - config.context = interpreter.getCachedContext(config.context); - } - } - - addAll(coll) { - for (let i = 0; i < coll.length; i++) { - this.add(coll[i]); - } - return false; - } - - equals(other) { - return this === other || - (other instanceof ATNConfigSet && - equalArrays(this.configs, other.configs) && - this.fullCtx === other.fullCtx && - this.uniqueAlt === other.uniqueAlt && - this.conflictingAlts === other.conflictingAlts && - this.hasSemanticContext === other.hasSemanticContext && - this.dipsIntoOuterContext === other.dipsIntoOuterContext); - } - - hashCode() { - const hash = new HashCode(); - hash.update(this.configs); - return hash.finish(); - } - - updateHashCode(hash) { - if (this.readOnly) { - if (this.cachedHashCode === -1) { - this.cachedHashCode = this.hashCode(); - } - hash.update(this.cachedHashCode); - } else { - hash.update(this.hashCode()); - } - } - - isEmpty() { - return this.configs.length === 0; - } - - contains(item) { - if (this.configLookup === null) { - throw "This method is not implemented for readonly sets."; - } - return this.configLookup.contains(item); - } - - containsFast(item) { - if (this.configLookup === null) { - throw "This method is not implemented for readonly sets."; - } - return this.configLookup.containsFast(item); - } - - clear() { - if (this.readOnly) { - throw "This set is readonly"; - } - this.configs = []; - this.cachedHashCode = -1; - this.configLookup = new HashSet(); - } - - setReadonly(readOnly) { - this.readOnly = readOnly; - if (readOnly) { - this.configLookup = null; // can't mod, no need for lookup cache - } - } - - toString() { - return arrayToString(this.configs) + - (this.hasSemanticContext ? ",hasSemanticContext=" + this.hasSemanticContext : "") + - (this.uniqueAlt !== ATN.INVALID_ALT_NUMBER ? ",uniqueAlt=" + this.uniqueAlt : "") + - (this.conflictingAlts !== null ? ",conflictingAlts=" + this.conflictingAlts : "") + - (this.dipsIntoOuterContext ? ",dipsIntoOuterContext" : ""); - } - - get items() { - return this.configs; - } - - get length() { - return this.configs.length; - } -} diff --git a/src/atn/ATNConfigSet.ts b/src/atn/ATNConfigSet.ts new file mode 100644 index 0000000..a9b2d5c --- /dev/null +++ b/src/atn/ATNConfigSet.ts @@ -0,0 +1,264 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable jsdoc/require-returns, jsdoc/require-param */ + +import { ATN } from "./ATN.js"; +import { SemanticContext } from "./SemanticContext.js"; +import { merge } from "./PredictionContextUtils.js"; +import { HashSet } from "../misc/HashSet.js"; +import { HashCode } from "../misc/HashCode.js"; + +import { equalArrays, arrayToString } from "../utils/helpers.js"; +import { ATNConfig } from "./ATNConfig.js"; +import { BitSet } from "../misc/BitSet.js"; +import { DoubleDict } from "../utils/DoubleDict.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { ATNState } from "./ATNState.js"; +import { ATNSimulator } from "./ATNSimulator.js"; + +const hashATNConfig = (c: ATNConfig) => { + return c.hashCodeForConfigSet(); +}; + +const equalATNConfigs = (a: ATNConfig, b: ATNConfig): boolean => { + if (a === b) { + return true; + } else if (a === null || b === null) { + return false; + } else { return a.equalsForConfigSet(b); } +}; + +/** + * Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track + * info about the set, with support for combining similar configurations using a + * graph-structured stack + */ +export class ATNConfigSet { + // Track the elements as they are added to the set; supports get(i)/// + public configs: ATNConfig[] = []; + + /** + * 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 + */ + public hasSemanticContext = false; + public dipsIntoOuterContext = false; + + /** + * Indicates that this configuration set is part of a full context + * 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; + + /** + * Indicates that the set of configurations is read-only. Do not + * allow any code to manipulate the set; DFA states will point at + * the sets and they must not change. This does not protect the other + * fields; in particular, conflictingAlts is set after + * we've made this readonly + */ + public readOnly = false; + + private cachedHashCode = -1; + + // TODO: add iterator for configs. + public constructor(fullCtx?: boolean) { + this.fullCtx = fullCtx ?? true; + } + + /** + * Adding a new config means merging contexts with existing configs for + * {@code (s, i, pi, _)}, where {@code s} is the + * {@link ATNConfig//state}, {@code i} is the {@link ATNConfig//alt}, and + * {@code pi} is the {@link ATNConfig//semanticContext}. We use + * {@code (s,i,pi)} as key. + * + *

            This method updates {@link dipsIntoOuterContext} and + * {@link hasSemanticContext} when necessary.

            + */ + public add(config: ATNConfig, + mergeCache?: DoubleDict | null): boolean { + if (mergeCache === undefined) { + mergeCache = null; + } + + 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); + 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(); + for (const config of this.configs) { + states.add(config.state); + } + + return states; + } + + public getPredicates(): SemanticContext[] { + const preds = []; + for (const config of this.configs) { + if (config.semanticContext !== SemanticContext.NONE) { + preds.push(config.semanticContext); + } + } + + return preds; + } + + public optimizeConfigs(interpreter: ATNSimulator): void { + if (this.readOnly) { + throw new Error("This set is readonly"); + } + + if (this.configLookup.length === 0) { + return; + } + + for (const config of this.configs) { + config.context = interpreter.getCachedContext(config.context!); + } + } + + public addAll(coll: ATNConfig[]): boolean { + for (const config of coll) { + this.add(config, null); + } + + return false; + } + + public equals(other: unknown): boolean { + return this === other || + (other instanceof ATNConfigSet && + equalArrays(this.configs, other.configs) && + this.fullCtx === other.fullCtx && + this.uniqueAlt === other.uniqueAlt && + this.conflictingAlts === other.conflictingAlts && + this.hasSemanticContext === other.hasSemanticContext && + this.dipsIntoOuterContext === other.dipsIntoOuterContext); + } + + public hashCode(): number { + const hash = new HashCode(); + hash.update(this.configs); + + return hash.finish(); + } + + public updateHashCode(hash: HashCode): void { + if (this.readOnly) { + if (this.cachedHashCode === -1) { + this.cachedHashCode = this.hashCode(); + } + hash.update(this.cachedHashCode); + } else { + hash.update(this.hashCode()); + } + } + + public isEmpty(): boolean { + return this.configs.length === 0; + } + + public contains(item: ATNConfig): boolean { + if (this.configLookup === null) { + throw new Error("This method is not implemented for readonly sets."); + } + + return this.configLookup.has(item); + } + + public containsFast(item: ATNConfig): boolean { + if (this.configLookup === null) { + throw new Error("This method is not implemented for readonly sets."); + } + + return this.configLookup.has(item); + } + + public clear(): void { + if (this.readOnly) { + throw new Error("This set is readonly"); + } + this.configs = []; + this.cachedHashCode = -1; + this.configLookup = new HashSet(); + } + + public setReadonly(readOnly: boolean): void { + this.readOnly = readOnly; + if (readOnly) { + this.configLookup = new HashSet(); // can't mod, no need for lookup cache + } + } + + public toString(): string { + return arrayToString(this.configs) + + (this.hasSemanticContext ? ",hasSemanticContext=" + this.hasSemanticContext : "") + + (this.uniqueAlt !== ATN.INVALID_ALT_NUMBER ? ",uniqueAlt=" + this.uniqueAlt : "") + + (this.conflictingAlts !== null ? ",conflictingAlts=" + this.conflictingAlts : "") + + (this.dipsIntoOuterContext ? ",dipsIntoOuterContext" : ""); + } + + public get items(): ATNConfig[] { + return this.configs; + } + + public get length(): number { + return this.configs.length; + } +} diff --git a/src/atn/ATNDeserializationOptions.d.ts b/src/atn/ATNDeserializationOptions.d.ts deleted file mode 100644 index aa60dc8..0000000 --- a/src/atn/ATNDeserializationOptions.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class ATNDeserializationOptions { - public readOnly?: boolean; - public verifyATN?: boolean; - public generateRuleBypassTransitions?: boolean; - -} diff --git a/src/atn/ATNDeserializationOptions.js b/src/atn/ATNDeserializationOptions.js deleted file mode 100644 index 29b762c..0000000 --- a/src/atn/ATNDeserializationOptions.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class ATNDeserializationOptions { - constructor(copyFrom) { - if (copyFrom === undefined) { - copyFrom = null; - } - this.readOnly = false; - this.verifyATN = copyFrom === null ? true : copyFrom.verifyATN; - this.generateRuleBypassTransitions = copyFrom === null ? false : copyFrom.generateRuleBypassTransitions; - } -} - -ATNDeserializationOptions.defaultOptions = new ATNDeserializationOptions(); -ATNDeserializationOptions.defaultOptions.readOnly = true; diff --git a/src/atn/ATNDeserializationOptions.ts b/src/atn/ATNDeserializationOptions.ts new file mode 100644 index 0000000..332ea5d --- /dev/null +++ b/src/atn/ATNDeserializationOptions.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +export class ATNDeserializationOptions { + public static readonly defaultOptions = new ATNDeserializationOptions(); + + public readOnly = false; + public verifyATN: boolean; + public generateRuleBypassTransitions: boolean; + + public constructor(copyFrom?: ATNDeserializationOptions) { + this.readOnly = false; + this.verifyATN = copyFrom == null ? true : copyFrom.verifyATN; + this.generateRuleBypassTransitions = copyFrom == null ? false : copyFrom.generateRuleBypassTransitions; + } + + static { + ATNDeserializationOptions.defaultOptions.readOnly = true; + } +} diff --git a/src/atn/ATNDeserializer.d.ts b/src/atn/ATNDeserializer.d.ts deleted file mode 100644 index 55a8fce..0000000 --- a/src/atn/ATNDeserializer.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNDeserializationOptions } from "./ATNDeserializationOptions.js"; -import { ATN } from "./ATN.js"; - -export declare class ATNDeserializer { - public constructor(options?: ATNDeserializationOptions); - public deserialize(data: number[]): ATN; -} diff --git a/src/atn/ATNDeserializer.js b/src/atn/ATNDeserializer.ts similarity index 54% rename from src/atn/ATNDeserializer.js rename to src/atn/ATNDeserializer.ts index 67d6733..13e590b 100644 --- a/src/atn/ATNDeserializer.js +++ b/src/atn/ATNDeserializer.ts @@ -4,154 +4,150 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from '../Token.js'; -import { ATN } from './ATN.js'; -import { ATNType } from './ATNType.js'; - -import { BasicState } from './BasicState.js'; -import { DecisionState } from './DecisionState.js'; -import { BlockStartState } from './BlockStartState.js'; -import { BlockEndState } from './BlockEndState.js'; -import { LoopEndState } from './LoopEndState.js'; -import { RuleStartState } from './RuleStartState.js'; -import { RuleStopState } from './RuleStopState.js'; -import { TokensStartState } from './TokensStartState.js'; -import { PlusLoopbackState } from './PlusLoopbackState.js'; -import { StarLoopbackState } from './StarLoopbackState.js'; -import { StarLoopEntryState } from './StarLoopEntryState.js'; -import { PlusBlockStartState } from './PlusBlockStartState.js'; -import { StarBlockStartState } from './StarBlockStartState.js'; -import { BasicBlockStartState } from './BasicBlockStartState.js'; - -import { AtomTransition } from './AtomTransition.js'; -import { SetTransition } from './SetTransition.js'; -import { NotSetTransition } from './NotSetTransition.js'; -import { RuleTransition } from './RuleTransition.js'; -import { RangeTransition } from './RangeTransition.js'; -import { ActionTransition } from './ActionTransition.js'; -import { EpsilonTransition } from './EpsilonTransition.js'; -import { WildcardTransition } from './WildcardTransition.js'; -import { PredicateTransition } from './PredicateTransition.js'; -import { PrecedencePredicateTransition } from './PrecedencePredicateTransition.js'; - -import { IntervalSet } from '../misc/IntervalSet.js'; -import { ATNDeserializationOptions } from './ATNDeserializationOptions.js'; - -import { LexerActionType } from './LexerActionType.js'; -import { LexerSkipAction } from './LexerSkipAction.js'; -import { LexerChannelAction } from './LexerChannelAction.js'; -import { LexerCustomAction } from './LexerCustomAction.js'; -import { LexerMoreAction } from './LexerMoreAction.js'; -import { LexerTypeAction } from './LexerTypeAction.js'; -import { LexerPushModeAction } from './LexerPushModeAction.js'; -import { LexerPopModeAction } from './LexerPopModeAction.js'; -import { LexerModeAction } from './LexerModeAction.js'; +/* eslint-disable @typescript-eslint/naming-convention */ + +import { Token } from "../Token.js"; +import { ATN } from "./ATN.js"; +import { ATNType } from "./ATNType.js"; + +import { BasicState } from "./BasicState.js"; +import { DecisionState } from "./DecisionState.js"; +import { BlockStartState } from "./BlockStartState.js"; +import { BlockEndState } from "./BlockEndState.js"; +import { LoopEndState } from "./LoopEndState.js"; +import { RuleStartState } from "./RuleStartState.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { TokensStartState } from "./TokensStartState.js"; +import { PlusLoopbackState } from "./PlusLoopbackState.js"; +import { StarLoopbackState } from "./StarLoopbackState.js"; +import { StarLoopEntryState } from "./StarLoopEntryState.js"; +import { PlusBlockStartState } from "./PlusBlockStartState.js"; +import { StarBlockStartState } from "./StarBlockStartState.js"; +import { BasicBlockStartState } from "./BasicBlockStartState.js"; + +import { AtomTransition } from "./AtomTransition.js"; +import { SetTransition } from "./SetTransition.js"; +import { NotSetTransition } from "./NotSetTransition.js"; +import { RuleTransition } from "./RuleTransition.js"; +import { RangeTransition } from "./RangeTransition.js"; +import { ActionTransition } from "./ActionTransition.js"; +import { EpsilonTransition } from "./EpsilonTransition.js"; +import { WildcardTransition } from "./WildcardTransition.js"; +import { PredicateTransition } from "./PredicateTransition.js"; +import { PrecedencePredicateTransition } from "./PrecedencePredicateTransition.js"; + +import { IntervalSet } from "../misc/IntervalSet.js"; +import { ATNDeserializationOptions } from "./ATNDeserializationOptions.js"; + +import { LexerActionType } from "./LexerActionType.js"; +import { LexerSkipAction } from "./LexerSkipAction.js"; +import { LexerChannelAction } from "./LexerChannelAction.js"; +import { LexerCustomAction } from "./LexerCustomAction.js"; +import { LexerMoreAction } from "./LexerMoreAction.js"; +import { LexerTypeAction } from "./LexerTypeAction.js"; +import { LexerPushModeAction } from "./LexerPushModeAction.js"; +import { LexerPopModeAction } from "./LexerPopModeAction.js"; +import { LexerModeAction } from "./LexerModeAction.js"; import { ATNStateType } from "./ATNStateType.js"; import { TransitionType } from "./TransitionType.js"; +import { ATNState } from "./ATNState.js"; +import { LexerAction } from "./LexerAction.js"; +import { Transition } from "./Transition.js"; -const SERIALIZED_VERSION = 4; - -function initArray(length, value) { - const tmp = []; +const initArray = (length: number, value: T) => { + const tmp = new Array(length - 1); tmp[length - 1] = value; - return tmp.map(function (i) { return value; }); -} + + return tmp.map(() => { return value; }); +}; export class ATNDeserializer { - constructor(options) { - if (options === undefined || options === null) { + public static readonly SERIALIZED_VERSION = 4; + + private data: number[] = []; + private pos = 0; + + private readonly deserializationOptions: ATNDeserializationOptions; + private stateFactories: Array<(() => ATNState) | null> | null = null; + private actionFactories: Array<(data1: number, data2: number) => LexerAction> | null = null; + + public constructor(options?: ATNDeserializationOptions) { + if (!options) { options = ATNDeserializationOptions.defaultOptions; } + this.deserializationOptions = options; this.stateFactories = null; this.actionFactories = null; } - deserialize(data) { - const legacy = this.reset(data); - this.checkVersion(legacy); - if (legacy) - this.skipUUID(); + public deserialize(data: number[]): ATN { + this.reset(data); + this.checkVersion(); + const atn = this.readATN(); - this.readStates(atn, legacy); - this.readRules(atn, legacy); + this.readStates(atn); + this.readRules(atn); this.readModes(atn); - const sets = []; - this.readSets(atn, sets, this.readInt.bind(this)); - if (legacy) - this.readSets(atn, sets, this.readInt32.bind(this)); + + const sets: IntervalSet[] = []; + this.readSets(atn, sets); this.readEdges(atn, sets); this.readDecisions(atn); - this.readLexerActions(atn, legacy); + this.readLexerActions(atn); this.markPrecedenceDecisions(atn); this.verifyATN(atn); + if (this.deserializationOptions.generateRuleBypassTransitions && atn.grammarType === ATNType.PARSER) { this.generateRuleBypassTransitions(atn); // re-verify after modification this.verifyATN(atn); } + return atn; } - reset(data) { - const version = data.charCodeAt ? data.charCodeAt(0) : data[0]; - if (version === SERIALIZED_VERSION - 1) { - const adjust = function (c) { - const v = c.charCodeAt(0); - return v > 1 ? v - 2 : v + 65534; - }; - const temp = data.split("").map(adjust); - // don't adjust the first value since that's the version number - temp[0] = data.charCodeAt(0); - this.data = temp; - this.pos = 0; - return true; - } else { - this.data = data; - this.pos = 0; - return false; - } - } + public reset(data: number[]): boolean { + this.data = data; + this.pos = 0; - skipUUID() { - let count = 0; - while (count++ < 8) - this.readInt(); + return false; } - checkVersion(legacy) { + public checkVersion(): void { const version = this.readInt(); - if (!legacy && version !== SERIALIZED_VERSION) { - throw ("Could not deserialize ATN with version " + version + " (expected " + SERIALIZED_VERSION + ")."); + if (version !== ATNDeserializer.SERIALIZED_VERSION) { + throw new Error("Could not deserialize ATN with version " + version + " (expected " + + ATNDeserializer.SERIALIZED_VERSION + ")."); } } - readATN() { + public readATN(): ATN { const grammarType = this.readInt(); const maxTokenType = this.readInt(); + return new ATN(grammarType, maxTokenType); } - readStates(atn, legacy) { - let j, pair, stateNumber; - const loopBackStateNumbers = []; - const endStateNumbers = []; - const nstates = this.readInt(); - for (let i = 0; i < nstates; i++) { - const stype = this.readInt(); + public readStates(atn: ATN): void { + let j; + let stateNumber; + + const loopBackStateNumbers: Array<[LoopEndState, number]> = []; + const endStateNumbers: Array<[BlockStartState, number]> = []; + const stateCount = this.readInt(); + for (let i = 0; i < stateCount; i++) { + const stateType = this.readInt(); // ignore bad type of states - if (stype === ATNStateType.INVALID_TYPE) { + if (stateType === ATNStateType.INVALID_TYPE) { atn.addState(null); continue; } - let ruleIndex = this.readInt(); - if (legacy && ruleIndex === 0xFFFF) { - ruleIndex = -1; - } - const s = this.stateFactory(stype, ruleIndex); - if (stype === ATNStateType.LOOP_END) { // special case + const ruleIndex = this.readInt(); + const s = this.stateFactory(stateType, ruleIndex); + if (stateType === ATNStateType.LOOP_END) { // special case const loopBackStateNumber = this.readInt(); - loopBackStateNumbers.push([s, loopBackStateNumber]); + loopBackStateNumbers.push([s as LoopEndState, loopBackStateNumber]); } else if (s instanceof BlockStartState) { const endStateNumber = this.readInt(); endStateNumbers.push([s, endStateNumber]); @@ -161,114 +157,112 @@ export class ATNDeserializer { // delay the assignment of loop back and end states until we know all the // state instances have been initialized for (j = 0; j < loopBackStateNumbers.length; j++) { - pair = loopBackStateNumbers[j]; + const pair = loopBackStateNumbers[j]; pair[0].loopBackState = atn.states[pair[1]]; } for (j = 0; j < endStateNumbers.length; j++) { - pair = endStateNumbers[j]; - pair[0].endState = atn.states[pair[1]]; + const pair = endStateNumbers[j]; + pair[0].endState = atn.states[pair[1]] as BlockEndState; } - let numNonGreedyStates = this.readInt(); + const numNonGreedyStates = this.readInt(); for (j = 0; j < numNonGreedyStates; j++) { stateNumber = this.readInt(); - atn.states[stateNumber].nonGreedy = true; + (atn.states[stateNumber] as DecisionState).nonGreedy = true; } - let numPrecedenceStates = this.readInt(); + const numPrecedenceStates = this.readInt(); for (j = 0; j < numPrecedenceStates; j++) { stateNumber = this.readInt(); - atn.states[stateNumber].isPrecedenceRule = true; + (atn.states[stateNumber] as RuleStartState).isPrecedenceRule = true; } } - readRules(atn, legacy) { + public readRules(atn: ATN): void { let i; - const nrules = this.readInt(); + const ruleCount = this.readInt(); if (atn.grammarType === ATNType.LEXER) { - atn.ruleToTokenType = initArray(nrules, 0); + atn.ruleToTokenType = initArray(ruleCount, 0); } - atn.ruleToStartState = initArray(nrules, 0); - for (i = 0; i < nrules; i++) { + atn.ruleToStartState = initArray(ruleCount, null); + for (i = 0; i < ruleCount; i++) { const s = this.readInt(); - atn.ruleToStartState[i] = atn.states[s]; + atn.ruleToStartState[i] = atn.states[s] as RuleStartState; if (atn.grammarType === ATNType.LEXER) { - let tokenType = this.readInt(); - if (legacy && tokenType === 0xFFFF) { - tokenType = Token.EOF; - } + const tokenType = this.readInt(); atn.ruleToTokenType[i] = tokenType; } } - atn.ruleToStopState = initArray(nrules, 0); + atn.ruleToStopState = initArray(ruleCount, null); for (i = 0; i < atn.states.length; i++) { const state = atn.states[i]; if (!(state instanceof RuleStopState)) { continue; } atn.ruleToStopState[state.ruleIndex] = state; - atn.ruleToStartState[state.ruleIndex].stopState = state; + atn.ruleToStartState[state.ruleIndex]!.stopState = state; } } - readModes(atn) { - const nmodes = this.readInt(); - for (let i = 0; i < nmodes; i++) { - let s = this.readInt(); - atn.modeToStartState.push(atn.states[s]); + public readModes(atn: ATN): void { + const modeCount = this.readInt(); + for (let i = 0; i < modeCount; i++) { + const s = this.readInt(); + atn.modeToStartState.push(atn.states[s] as RuleStartState); } } - readSets(atn, sets, reader) { + public readSets(atn: ATN, sets: IntervalSet[]): void { const m = this.readInt(); for (let i = 0; i < m; i++) { - const iset = new IntervalSet(); - sets.push(iset); + const intervalSet = new IntervalSet(); + sets.push(intervalSet); const n = this.readInt(); const containsEof = this.readInt(); if (containsEof !== 0) { - iset.addOne(-1); + intervalSet.addOne(-1); } for (let j = 0; j < n; j++) { - const i1 = reader(); - const i2 = reader(); - iset.addRange(i1, i2); + const i1 = this.readInt(); + const i2 = this.readInt(); + intervalSet.addRange(i1, i2); } } } - readEdges(atn, sets) { - let i, j, state, trans, target; - const nedges = this.readInt(); - for (i = 0; i < nedges; i++) { + public readEdges(atn: ATN, sets: IntervalSet[]): void { + let i; let j; let state; let trans; let target; + const edgeCount = this.readInt(); + for (i = 0; i < edgeCount; i++) { const src = this.readInt(); const trg = this.readInt(); const ttype = this.readInt(); const arg1 = this.readInt(); const arg2 = this.readInt(); const arg3 = this.readInt(); - trans = this.edgeFactory(atn, ttype, src, trg, arg1, arg2, arg3, sets); - const srcState = atn.states[src]; + trans = this.edgeFactory(atn, ttype, trg, arg1, arg2, arg3, sets); + const srcState = atn.states[src]!; srcState.addTransition(trans); } + // edges for rule stop states can be derived, so they aren't serialized for (i = 0; i < atn.states.length; i++) { - state = atn.states[i]; + state = atn.states[i]!; for (j = 0; j < state.transitions.length; j++) { const t = state.transitions[j]; if (!(t instanceof RuleTransition)) { continue; } let outermostPrecedenceReturn = -1; - if (atn.ruleToStartState[t.target.ruleIndex].isPrecedenceRule) { + if (atn.ruleToStartState[t.target.ruleIndex]!.isPrecedenceRule) { if (t.precedence === 0) { outermostPrecedenceReturn = t.target.ruleIndex; } } trans = new EpsilonTransition(t.followState, outermostPrecedenceReturn); - atn.ruleToStopState[t.target.ruleIndex].addTransition(trans); + atn.ruleToStopState[t.target.ruleIndex]!.addTransition(trans); } } @@ -277,12 +271,12 @@ export class ATNDeserializer { if (state instanceof BlockStartState) { // we need to know the end state to set its start state if (state.endState === null) { - throw ("IllegalState"); + throw new Error("IllegalState"); } // block end states can only be associated to a single block start // state if (state.endState.startState) { - throw ("IllegalState"); + throw new Error("IllegalState"); } state.endState.startState = state; } @@ -304,36 +298,30 @@ export class ATNDeserializer { } } - readDecisions(atn) { - const ndecisions = this.readInt(); - for (let i = 0; i < ndecisions; i++) { + public readDecisions(atn: ATN): void { + const decisionCount = this.readInt(); + for (let i = 0; i < decisionCount; i++) { const s = this.readInt(); - const decState = atn.states[s]; + const decState = atn.states[s] as DecisionState; atn.decisionToState.push(decState); decState.decision = i; } } - readLexerActions(atn, legacy) { + public readLexerActions(atn: ATN): void { if (atn.grammarType === ATNType.LEXER) { const count = this.readInt(); - atn.lexerActions = initArray(count, null); + atn.lexerActions = []; for (let i = 0; i < count; i++) { const actionType = this.readInt(); - let data1 = this.readInt(); - if (legacy && data1 === 0xFFFF) { - data1 = -1; - } - let data2 = this.readInt(); - if (legacy && data2 === 0xFFFF) { - data2 = -1; - } - atn.lexerActions[i] = this.lexerActionFactory(actionType, data1, data2); + const data1 = this.readInt(); + const data2 = this.readInt(); + atn.lexerActions.push(this.lexerActionFactory(actionType, data1, data2)); } } } - generateRuleBypassTransitions(atn) { + public generateRuleBypassTransitions(atn: ATN): void { let i; const count = atn.ruleToStartState.length; for (i = 0; i < count; i++) { @@ -344,8 +332,8 @@ export class ATNDeserializer { } } - generateRuleBypassTransition(atn, idx) { - let i, state; + public generateRuleBypassTransition(atn: ATN, idx: number): void { + let i; let state; const bypassStart = new BasicBlockStartState(); bypassStart.ruleIndex = idx; atn.addState(bypassStart); @@ -362,30 +350,29 @@ export class ATNDeserializer { let excludeTransition = null; let endState = null; - if (atn.ruleToStartState[idx].isPrecedenceRule) { + if (atn.ruleToStartState[idx]!.isPrecedenceRule) { // wrap from the beginning of the rule to the StarLoopEntryState endState = null; for (i = 0; i < atn.states.length; i++) { - state = atn.states[i]; + state = atn.states[i] as LoopEndState; if (this.stateIsEndStateFor(state, idx)) { endState = state; - excludeTransition = state.loopBackState.transitions[0]; + excludeTransition = state.loopBackState!.transitions[0]; break; } } if (excludeTransition === null) { - throw ("Couldn't identify final state of the precedence rule prefix section."); + throw new Error("Couldn't identify final state of the precedence rule prefix section."); } } else { - endState = atn.ruleToStopState[idx]; + endState = atn.ruleToStopState[idx]!; } // all non-excluded transitions that currently target end state need to // target blockEnd instead for (i = 0; i < atn.states.length; i++) { - state = atn.states[i]; - for (let j = 0; j < state.transitions.length; j++) { - const transition = state.transitions[j]; + state = atn.states[i]!; + for (const transition of state.transitions) { if (transition === excludeTransition) { continue; } @@ -397,15 +384,17 @@ export class ATNDeserializer { // all transitions leaving the rule start state need to leave blockStart // instead - const ruleToStartState = atn.ruleToStartState[idx]; + const ruleToStartState = atn.ruleToStartState[idx]!; const count = ruleToStartState.transitions.length; while (count > 0) { bypassStart.addTransition(ruleToStartState.transitions[count - 1]); ruleToStartState.transitions = ruleToStartState.transitions.slice(-1); } // link the new states - atn.ruleToStartState[idx].addTransition(new EpsilonTransition(bypassStart)); - bypassStop.addTransition(new EpsilonTransition(endState)); + atn.ruleToStartState[idx]!.addTransition(new EpsilonTransition(bypassStart)); + if (endState) { + bypassStop.addTransition(new EpsilonTransition(endState)); + } const matchState = new BasicState(); atn.addState(matchState); @@ -413,7 +402,7 @@ export class ATNDeserializer { bypassStart.addTransition(new EpsilonTransition(matchState)); } - stateIsEndStateFor(state, idx) { + public stateIsEndStateFor(state: ATNState, idx: number): ATNState | null { if (state.ruleIndex !== idx) { return null; } @@ -435,18 +424,18 @@ export class ATNDeserializer { /** * Analyze the {@link StarLoopEntryState} states in the specified ATN to set * the {@link StarLoopEntryState} field to the correct value. + * * @param atn The ATN. */ - markPrecedenceDecisions(atn) { - for (let i = 0; i < atn.states.length; i++) { - const state = atn.states[i]; + public markPrecedenceDecisions(atn: ATN): void { + for (const state of atn.states) { if (!(state instanceof StarLoopEntryState)) { continue; } // We analyze the ATN to determine if this ATN decision state is the // decision for the closure block that determines whether a // precedence rule should continue or complete. - if (atn.ruleToStartState[state.ruleIndex].isPrecedenceRule) { + if (atn.ruleToStartState[state.ruleIndex]!.isPrecedenceRule) { const maybeLoopEndState = state.transitions[state.transitions.length - 1].target; if (maybeLoopEndState instanceof LoopEndState) { if (maybeLoopEndState.epsilonOnlyTransitions && @@ -458,16 +447,17 @@ export class ATNDeserializer { } } - verifyATN(atn) { + public verifyATN(atn: ATN): void { if (!this.deserializationOptions.verifyATN) { return; } + // verify assumptions - for (let i = 0; i < atn.states.length; i++) { - const state = atn.states[i]; + for (const state of atn.states) { if (state === null) { continue; } + this.checkCondition(state.epsilonOnlyTransitions || state.transitions.length <= 1); if (state instanceof PlusBlockStartState) { this.checkCondition(state.loopBackState !== null); @@ -481,7 +471,7 @@ export class ATNDeserializer { this.checkCondition(state.transitions[1].target instanceof StarBlockStartState); this.checkCondition(state.nonGreedy); } else { - throw ("IllegalState"); + throw new Error("IllegalState"); } } else if (state instanceof StarLoopbackState) { this.checkCondition(state.transitions.length === 1); @@ -502,7 +492,7 @@ export class ATNDeserializer { } } - checkCondition(condition, message) { + public checkCondition(condition: boolean, message?: string): void { if (!condition) { if (message === undefined || message === null) { message = "IllegalState"; @@ -511,25 +501,22 @@ export class ATNDeserializer { } } - readInt() { + public readInt(): number { return this.data[this.pos++]; } - readInt32() { - const low = this.readInt(); - const high = this.readInt(); - return low | (high << 16); - } - - edgeFactory(atn, type, src, trg, arg1, arg2, arg3, sets) { - const target = atn.states[trg]; + public edgeFactory(atn: ATN, type: number, trg: number, arg1: number, arg2: number, + arg3: number, sets: IntervalSet[]): Transition { + const target = atn.states[trg]!; switch (type) { case TransitionType.EPSILON: return new EpsilonTransition(target); case TransitionType.RANGE: - return arg3 !== 0 ? new RangeTransition(target, Token.EOF, arg2) : new RangeTransition(target, arg1, arg2); + return arg3 !== 0 + ? new RangeTransition(target, Token.EOF, arg2) + : new RangeTransition(target, arg1, arg2); case TransitionType.RULE: - return new RuleTransition(atn.states[arg1], arg2, arg3, target); + return new RuleTransition(atn.states[arg1]!, arg2, arg3, target); case TransitionType.PREDICATE: return new PredicateTransition(target, arg1, arg2, arg3 !== 0); case TransitionType.PRECEDENCE: @@ -545,55 +532,75 @@ export class ATNDeserializer { case TransitionType.WILDCARD: return new WildcardTransition(target); default: - throw "The specified transition type: " + type + " is not valid."; + throw new Error("The specified transition type: " + type + " is not valid."); } } - stateFactory(type, ruleIndex) { + public stateFactory(type: number, ruleIndex: number): ATNState | null { if (this.stateFactories === null) { - const sf = []; + const sf: Array<(() => ATNState) | null> = []; sf[ATNStateType.INVALID_TYPE] = null; - sf[ATNStateType.BASIC] = () => new BasicState(); - sf[ATNStateType.RULE_START] = () => new RuleStartState(); - sf[ATNStateType.BLOCK_START] = () => new BasicBlockStartState(); - sf[ATNStateType.PLUS_BLOCK_START] = () => new PlusBlockStartState(); - sf[ATNStateType.STAR_BLOCK_START] = () => new StarBlockStartState(); - sf[ATNStateType.TOKEN_START] = () => new TokensStartState(); - sf[ATNStateType.RULE_STOP] = () => new RuleStopState(); - sf[ATNStateType.BLOCK_END] = () => new BlockEndState(); - sf[ATNStateType.STAR_LOOP_BACK] = () => new StarLoopbackState(); - sf[ATNStateType.STAR_LOOP_ENTRY] = () => new StarLoopEntryState(); - sf[ATNStateType.PLUS_LOOP_BACK] = () => new PlusLoopbackState(); - sf[ATNStateType.LOOP_END] = () => new LoopEndState(); + sf[ATNStateType.BASIC] = () => { return new BasicState(); }; + sf[ATNStateType.RULE_START] = () => { return new RuleStartState(); }; + sf[ATNStateType.BLOCK_START] = () => { return new BasicBlockStartState(); }; + sf[ATNStateType.PLUS_BLOCK_START] = () => { return new PlusBlockStartState(); }; + sf[ATNStateType.STAR_BLOCK_START] = () => { return new StarBlockStartState(); }; + sf[ATNStateType.TOKEN_START] = () => { return new TokensStartState(); }; + sf[ATNStateType.RULE_STOP] = () => { return new RuleStopState(); }; + sf[ATNStateType.BLOCK_END] = () => { return new BlockEndState(); }; + sf[ATNStateType.STAR_LOOP_BACK] = () => { return new StarLoopbackState(); }; + sf[ATNStateType.STAR_LOOP_ENTRY] = () => { return new StarLoopEntryState(); }; + sf[ATNStateType.PLUS_LOOP_BACK] = () => { return new PlusLoopbackState(); }; + sf[ATNStateType.LOOP_END] = () => { return new LoopEndState(); }; this.stateFactories = sf; } if (type > this.stateFactories.length || this.stateFactories[type] === null) { - throw ("The specified state type " + type + " is not valid."); + throw new Error("The specified state type " + type + " is not valid."); } else { - const s = this.stateFactories[type](); + const s = this.stateFactories[type]?.() ?? null; if (s !== null) { s.ruleIndex = ruleIndex; + return s; } } + + return null; } - lexerActionFactory(type, data1, data2) { + public lexerActionFactory(type: number, data1: number, data2: number): LexerAction { if (this.actionFactories === null) { const af = []; - af[LexerActionType.CHANNEL] = (data1, data2) => new LexerChannelAction(data1); - af[LexerActionType.CUSTOM] = (data1, data2) => new LexerCustomAction(data1, data2); - af[LexerActionType.MODE] = (data1, data2) => new LexerModeAction(data1); - af[LexerActionType.MORE] = (data1, data2) => LexerMoreAction.INSTANCE; - af[LexerActionType.POP_MODE] = (data1, data2) => LexerPopModeAction.INSTANCE; - af[LexerActionType.PUSH_MODE] = (data1, data2) => new LexerPushModeAction(data1); - af[LexerActionType.SKIP] = (data1, data2) => LexerSkipAction.INSTANCE; - af[LexerActionType.TYPE] = (data1, data2) => new LexerTypeAction(data1); + af[LexerActionType.CHANNEL] = (data1: number, _data2: number) => { + return new LexerChannelAction(data1); + }; + af[LexerActionType.CUSTOM] = (data1: number, data2: number) => { + return new LexerCustomAction(data1, data2); + }; + af[LexerActionType.MODE] = (data1: number, _data2: number) => { + return new LexerModeAction(data1); + }; + af[LexerActionType.MORE] = (_data1: number, _data2: number) => { + return LexerMoreAction.INSTANCE; + }; + af[LexerActionType.POP_MODE] = (_data1: number, _data2: number) => { + return LexerPopModeAction.INSTANCE; + }; + af[LexerActionType.PUSH_MODE] = (data1: number, _data2: number) => { + return new LexerPushModeAction(data1); + }; + af[LexerActionType.SKIP] = (_data1: number, _data2: number) => { + return LexerSkipAction.INSTANCE; + }; + af[LexerActionType.TYPE] = (data1: number, _data2: number) => { + return new LexerTypeAction(data1); + }; this.actionFactories = af; } + if (type > this.actionFactories.length || this.actionFactories[type] === null) { - throw ("The specified lexer action type " + type + " is not valid."); + throw new Error("The specified lexer action type " + type + " is not valid."); } else { return this.actionFactories[type](data1, data2); } diff --git a/src/atn/ATNSimulator.d.ts b/src/atn/ATNSimulator.d.ts deleted file mode 100644 index 92ae82f..0000000 --- a/src/atn/ATNSimulator.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class ATNSimulator { -} diff --git a/src/atn/ATNSimulator.js b/src/atn/ATNSimulator.js deleted file mode 100644 index 96eeb27..0000000 --- a/src/atn/ATNSimulator.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DFAState } from '../dfa/DFAState.js'; -import { ATNConfigSet } from './ATNConfigSet.js'; -import { getCachedPredictionContext } from './PredictionContextUtils.js'; -import { HashMap } from "../misc/HashMap.js"; - -export class ATNSimulator { - constructor(atn, sharedContextCache) { - /** - * The context cache maps all PredictionContext objects that are == - * to a single cached copy. This cache is shared across all contexts - * in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet - * to use only cached nodes/graphs in addDFAState(). We don't want to - * fill this during closure() since there are lots of contexts that - * pop up but are not used ever again. It also greatly slows down closure(). - * - *

            This cache makes a huge difference in memory and a little bit in speed. - * For the Java grammar on java.*, it dropped the memory requirements - * at the end from 25M to 16M. We don't store any of the full context - * graphs in the DFA because they are limited to local context only, - * but apparently there's a lot of repetition there as well. We optimize - * the config contexts before storing the config set in the DFA states - * by literally rebuilding them with cached subgraphs only.

            - * - *

            I tried a cache for use during closure operations, that was - * whacked after each adaptivePredict(). It cost a little bit - * more time I think and doesn't save on the overall footprint - * so it's not worth the complexity.

            - */ - this.atn = atn; - this.sharedContextCache = sharedContextCache; - return this; - } - - getCachedContext(context) { - if (this.sharedContextCache === null) { - return context; - } - const visited = new HashMap(); - return getCachedPredictionContext(context, this.sharedContextCache, visited); - } -} - -// Must distinguish between missing edge and edge we know leads nowhere/// -ATNSimulator.ERROR = new DFAState(0x7FFFFFFF, new ATNConfigSet()); diff --git a/src/atn/ATNSimulator.ts b/src/atn/ATNSimulator.ts new file mode 100644 index 0000000..34a68f8 --- /dev/null +++ b/src/atn/ATNSimulator.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { DFAState } from "../dfa/DFAState.js"; +import { getCachedPredictionContext } from "./PredictionContextUtils.js"; +import { HashMap } from "../misc/HashMap.js"; +import { ATN } from "./ATN.js"; +import { PredictionContextCache } from "./PredictionContextCache.js"; +import { PredictionContext } from "./PredictionContext.js"; + +export class ATNSimulator { + /** Must distinguish between missing edge and edge we know leads nowhere */ + + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly ERROR = new DFAState(0x7FFFFFFF); + + public readonly atn: ATN; + + /** + * The context cache maps all PredictionContext objects that are == + * to a single cached copy. This cache is shared across all contexts + * in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet + * to use only cached nodes/graphs in addDFAState(). We don't want to + * fill this during closure() since there are lots of contexts that + * pop up but are not used ever again. It also greatly slows down closure(). + * + *

            This cache makes a huge difference in memory and a little bit in speed. + * For the Java grammar on java.*, it dropped the memory requirements + * at the end from 25M to 16M. We don't store any of the full context + * graphs in the DFA because they are limited to local context only, + * but apparently there's a lot of repetition there as well. We optimize + * the config contexts before storing the config set in the DFA states + * by literally rebuilding them with cached subgraphs only.

            + * + *

            I tried a cache for use during closure operations, that was + * whacked after each adaptivePredict(). It cost a little bit + * more time I think and doesn't save on the overall footprint + * so it's not worth the complexity.

            + */ + protected readonly sharedContextCache: PredictionContextCache | null = null; + + public constructor(atn: ATN, sharedContextCache: PredictionContextCache | null) { + + this.atn = atn; + this.sharedContextCache = sharedContextCache; + + return this; + } + + public getCachedContext(context: PredictionContext): PredictionContext { + if (this.sharedContextCache === null) { + return context; + } + const visited = new HashMap(); + + return getCachedPredictionContext(context, this.sharedContextCache, visited); + } +} diff --git a/src/atn/ATNState.d.ts b/src/atn/ATNState.d.ts deleted file mode 100644 index a85a4b3..0000000 --- a/src/atn/ATNState.d.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATN } from "./ATN.js"; -import { Transition } from "./Transition.js"; - -import type { EpsilonTransition } from "./EpsilonTransition.js"; -import type { BasicState } from "./BasicState.js"; - -/** - * The following images show the relation of states and - * {@link ATNState} for various grammar constructs. - * - *
              - * - *
            • Solid edges marked with an &//0949; indicate a required - * {@link EpsilonTransition}.
            • - * - *
            • Dashed edges indicate locations where any transition derived from - * {@link Transition} might appear.
            • - * - *
            • Dashed nodes are place holders for either a sequence of linked - * {@link BasicState} states or the inclusion of a block representing a nested - * construct in one of the forms below.
            • - * - *
            • Nodes showing multiple outgoing alternatives with a {@code ...} support - * any number of alternatives (one or more). Nodes without the {@code ...} only - * support the exact number of alternatives shown in the diagram.
            • - * - *
            - * - *

            Basic Blocks

            - * - *

            Rule

            - * - * - * - *

            Block of 1 or more alternatives

            - * - * - * - *

            Greedy Loops

            - * - *

            Greedy Closure: {@code (...)*}

            - * - * - * - *

            Greedy Positive Closure: {@code (...)+}

            - * - * - * - *

            Greedy Optional: {@code (...)?}

            - * - * - * - *

            Non-Greedy Loops

            - * - *

            Non-Greedy Closure: {@code (...)*?}

            - * - * - * - *

            Non-Greedy Positive Closure: {@code (...)+?}

            - * - * - * - *

            Non-Greedy Optional: {@code (...)??}

            - * - * - */ -export declare class ATNState { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static readonly INVALID_STATE_NUMBER: number; - - public get stateType(): number; - - public atn: ATN | null; - public stateNumber: number; - public ruleIndex: number; - public epsilonOnlyTransitions: boolean; - public transitions: Transition[]; - - public constructor(); - - public toString(): string; - public equals(other: unknown): boolean; - public isNonGreedyExitState(): boolean; - public addTransition(transition: Transition, index: number): void; -} diff --git a/src/atn/ATNState.js b/src/atn/ATNState.ts similarity index 59% rename from src/atn/ATNState.js rename to src/atn/ATNState.ts index f58562e..ffaf01a 100644 --- a/src/atn/ATNState.js +++ b/src/atn/ATNState.ts @@ -4,8 +4,23 @@ * can be found in the LICENSE.txt file in the project root. */ -export class ATNState { - constructor() { +import { IntervalSet } from "../misc/IntervalSet.js"; +import { IComparable } from "../utils/helpers.js"; +import { ATN } from "./ATN.js"; +import { Transition } from "./Transition.js"; + +export class ATNState implements IComparable { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly INVALID_STATE_NUMBER = -1; + + public atn: ATN | null; + public stateNumber: number; + public ruleIndex: number; + public epsilonOnlyTransitions: boolean; + public nextTokenWithinRule: IntervalSet | null; + public transitions: Transition[]; + + public constructor() { // Which ATN are we in? this.atn = null; this.ruleIndex = 0; // at runtime, we don't have Rule objects @@ -18,15 +33,19 @@ export class ATNState { this.nextTokenWithinRule = null; } - get stateType() { + public get stateType(): number { return ATNState.INVALID_STATE_NUMBER; } - toString() { + public toString(): string { return `${this.stateNumber}`; } - equals(other) { + public hashCode(): number { + return this.stateNumber; + } + + public equals(other: unknown): boolean { if (other instanceof ATNState) { return this.stateNumber === other.stateNumber; } else { @@ -34,19 +53,21 @@ export class ATNState { } } - isNonGreedyExitState() { + public isNonGreedyExitState(): boolean { return false; } - addTransition(trans, index) { + public addTransition(trans: Transition, index?: number): void { if (index === undefined) { index = -1; } + if (this.transitions.length === 0) { this.epsilonOnlyTransitions = trans.isEpsilon; } else if (this.epsilonOnlyTransitions !== trans.isEpsilon) { this.epsilonOnlyTransitions = false; } + if (index === -1) { this.transitions.push(trans); } else { @@ -54,5 +75,3 @@ export class ATNState { } } } - -ATNState.INVALID_STATE_NUMBER = -1; diff --git a/src/atn/ATNStateType.d.ts b/src/atn/ATNStateType.d.ts deleted file mode 100644 index f1a268f..0000000 --- a/src/atn/ATNStateType.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/* eslint-disable @typescript-eslint/naming-convention */ - -export declare class ATNStateType { - public static readonly INVALID_TYPE: number; - public static readonly BASIC: number; - public static readonly RULE_START: number; - public static readonly BLOCK_START: number; - public static readonly PLUS_BLOCK_START: number; - public static readonly STAR_BLOCK_START: number; - public static readonly TOKEN_START: number; - public static readonly RULE_STOP: number; - public static readonly BLOCK_END: number; - public static readonly STAR_LOOP_BACK: number; - public static readonly STAR_LOOP_ENTRY: number; - public static readonly PLUS_LOOP_BACK: number; - public static readonly LOOP_END: number; -} diff --git a/src/atn/ATNStateType.js b/src/atn/ATNStateType.ts similarity index 87% rename from src/atn/ATNStateType.js rename to src/atn/ATNStateType.ts index 8359e00..ed49767 100644 --- a/src/atn/ATNStateType.js +++ b/src/atn/ATNStateType.ts @@ -4,6 +4,7 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable @typescript-eslint/naming-convention */ export const ATNStateType = { INVALID_TYPE: 0, BASIC: 1, @@ -18,4 +19,4 @@ export const ATNStateType = { STAR_LOOP_ENTRY: 10, PLUS_LOOP_BACK: 11, LOOP_END: 12, -}; +} as const; diff --git a/src/atn/ATNType.js b/src/atn/ATNType.ts similarity index 80% rename from src/atn/ATNType.js rename to src/atn/ATNType.ts index 8a3f0b6..c94c5f5 100644 --- a/src/atn/ATNType.js +++ b/src/atn/ATNType.ts @@ -4,10 +4,12 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable @typescript-eslint/naming-convention */ + /** * Represents the type of recognizer an ATN applies to */ export const ATNType = { LEXER: 0, - PARSER: 1 + PARSER: 1, }; diff --git a/src/atn/AbstractPredicateTransition.js b/src/atn/AbstractPredicateTransition.js deleted file mode 100644 index 8392456..0000000 --- a/src/atn/AbstractPredicateTransition.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Transition } from "./Transition.js"; - -export class AbstractPredicateTransition extends Transition { - constructor(target) { - super(target); - } -} diff --git a/src/atn/AbstractPredicateTransition.d.ts b/src/atn/AbstractPredicateTransition.ts similarity index 66% rename from src/atn/AbstractPredicateTransition.d.ts rename to src/atn/AbstractPredicateTransition.ts index 6055173..5219013 100644 --- a/src/atn/AbstractPredicateTransition.d.ts +++ b/src/atn/AbstractPredicateTransition.ts @@ -7,6 +7,8 @@ import { ATNState } from "./ATNState.js"; import { Transition } from "./Transition.js"; -export declare abstract class AbstractPredicateTransition extends Transition { - public constructor(target: ATNState); +export abstract class AbstractPredicateTransition extends Transition { + public constructor(target: ATNState) { + super(target); + } } diff --git a/src/atn/ActionTransition.d.ts b/src/atn/ActionTransition.d.ts deleted file mode 100644 index 89b4af4..0000000 --- a/src/atn/ActionTransition.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class ActionTransition extends Transition { - public ruleIndex: number; - public actionIndex: number; - public isCtxDependent: boolean; - public isEpsilon: boolean; - - public constructor(target: ATNState, ruleIndex: number, actionIndex: number, isCtxDependent: boolean); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/ActionTransition.js b/src/atn/ActionTransition.ts similarity index 55% rename from src/atn/ActionTransition.js rename to src/atn/ActionTransition.ts index a3b1c7d..a9fe850 100644 --- a/src/atn/ActionTransition.js +++ b/src/atn/ActionTransition.ts @@ -4,24 +4,35 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ATNState } from "./ATNState.js"; import { Transition } from "./Transition.js"; import { TransitionType } from "./TransitionType.js"; export class ActionTransition extends Transition { - constructor(target, ruleIndex, actionIndex, isCtxDependent) { + public ruleIndex: number; + public actionIndex: number; + public isCtxDependent: boolean; + + public constructor(target: ATNState, ruleIndex: number, actionIndex: number, isCtxDependent: boolean) { super(target); - this.serializationType = TransitionType.ACTION; this.ruleIndex = ruleIndex; this.actionIndex = actionIndex === undefined ? -1 : actionIndex; this.isCtxDependent = isCtxDependent === undefined ? false : isCtxDependent; // e.g., $i ref in pred - this.isEpsilon = true; } - matches(symbol, minVocabSymbol, maxVocabSymbol) { + public override get isEpsilon(): boolean { + return true; + } + + public get serializationType(): number { + return TransitionType.ACTION; + } + + public matches(_symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { return false; } - toString() { + public override toString(): string { return "action_" + this.ruleIndex + ":" + this.actionIndex; } } diff --git a/src/atn/ArrayPredictionContext.js b/src/atn/ArrayPredictionContext.ts similarity index 67% rename from src/atn/ArrayPredictionContext.js rename to src/atn/ArrayPredictionContext.ts index bed4683..15de086 100644 --- a/src/atn/ArrayPredictionContext.js +++ b/src/atn/ArrayPredictionContext.ts @@ -5,17 +5,20 @@ */ import { PredictionContext } from "./PredictionContext.js"; -import { equalArrays } from "../utils/equalArrays.js"; import { HashCode } from "../misc/HashCode.js"; +import { equalArrays } from "../utils/helpers.js"; + export class ArrayPredictionContext extends PredictionContext { + public readonly parents: Array = []; + public readonly returnStates: number[] = []; - constructor(parents, returnStates) { + public constructor(parents: Array, returnStates: number[]) { /** * Parent can be null only if full ctx mode and we make an array - * from {@link //EMPTY} and non-empty. We merge {@link //EMPTY} by using + * from {@link EMPTY} and non-empty. We merge {@link EMPTY} by using * null parent and - * returnState == {@link //EMPTY_RETURN_STATE}. + * returnState == {@link EMPTY_RETURN_STATE}. */ const h = new HashCode(); h.update(parents, returnStates); @@ -23,37 +26,43 @@ export class ArrayPredictionContext extends PredictionContext { super(hashCode); this.parents = parents; this.returnStates = returnStates; + return this; } - isEmpty() { + public override isEmpty(): boolean { // since EMPTY_RETURN_STATE can only appear in the last position, we // don't need to verify that size==1 return this.returnStates[0] === PredictionContext.EMPTY_RETURN_STATE; } - getParent(index) { + public getParent(index: number): PredictionContext | null { return this.parents[index]; } - getReturnState(index) { + public getReturnState(index: number): number { return this.returnStates[index]; } - equals(other) { + public equals(other: unknown): boolean { if (this === other) { return true; - } else if (!(other instanceof ArrayPredictionContext)) { + } + + if (!(other instanceof ArrayPredictionContext)) { return false; - } else if (this.hashCode() !== other.hashCode()) { + } + + if (this.hashCode() !== other.hashCode()) { return false; // can't be same if hash is different - } else { - return equalArrays(this.returnStates, other.returnStates) && - equalArrays(this.parents, other.parents); } + + return equalArrays(this.returnStates, other.returnStates) && + equalArrays(this.parents, other.parents); + } - toString() { + public override toString(): string { if (this.isEmpty()) { return "[]"; } else { @@ -73,11 +82,12 @@ export class ArrayPredictionContext extends PredictionContext { s = s + "null"; } } + return s + "]"; } } - get length() { + public get length(): number { return this.returnStates.length; } } diff --git a/src/atn/AtomTransition.d.ts b/src/atn/AtomTransition.d.ts deleted file mode 100644 index dc108ac..0000000 --- a/src/atn/AtomTransition.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class AtomTransition extends Transition { - public constructor(target: ATNState, label: number); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/AtomTransition.js b/src/atn/AtomTransition.js deleted file mode 100644 index 8694c6c..0000000 --- a/src/atn/AtomTransition.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntervalSet } from "../misc/IntervalSet.js"; -import { Transition } from "./Transition.js"; -import { TransitionType } from "./TransitionType.js"; - -export class AtomTransition extends Transition { - constructor(target, label) { - super(target); - // The token type or character value; or, signifies special label. - this.label_ = label; - this.label = this.makeLabel(); - this.serializationType = TransitionType.ATOM; - } - - makeLabel() { - const s = new IntervalSet(); - s.addOne(this.label_); - return s; - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return this.label_ === symbol; - } - - toString() { - return this.label_; - } -} diff --git a/src/atn/AtomTransition.ts b/src/atn/AtomTransition.ts new file mode 100644 index 0000000..f58241b --- /dev/null +++ b/src/atn/AtomTransition.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { IntervalSet } from "../misc/IntervalSet.js"; +import { Transition } from "./Transition.js"; +import { TransitionType } from "./TransitionType.js"; +import { ATNState } from "./ATNState.js"; + +export class AtomTransition extends Transition { + /** The token type or character value; or, signifies special label. */ + public labelValue: number; + + #label: IntervalSet; + + public constructor(target: ATNState, label: number) { + super(target); + this.labelValue = label; + this.#label = IntervalSet.of(label, label); + } + + public override get label(): IntervalSet { + return this.#label; + } + + public override matches(symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return this.labelValue === symbol; + } + + public override get serializationType(): number { + return TransitionType.ATOM; + } + + public override toString(): string { + return this.labelValue.toString(); + } +} diff --git a/src/atn/BasicBlockStartState.js b/src/atn/BasicBlockStartState.ts similarity index 89% rename from src/atn/BasicBlockStartState.js rename to src/atn/BasicBlockStartState.ts index aed117d..77c4334 100644 --- a/src/atn/BasicBlockStartState.js +++ b/src/atn/BasicBlockStartState.ts @@ -8,7 +8,7 @@ import { ATNStateType } from "./ATNStateType.js"; import { BlockStartState } from "./BlockStartState.js"; export class BasicBlockStartState extends BlockStartState { - get stateType() { + public override get stateType(): number { return ATNStateType.BLOCK_START; } diff --git a/src/atn/BasicState.d.ts b/src/atn/BasicState.d.ts deleted file mode 100644 index 59939a3..0000000 --- a/src/atn/BasicState.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright 2016 The ANTLR Project. All rights reserved. - * Licensed under the BSD-3-Clause license. See LICENSE file in the project root for license information. - */ - -import { ATNState } from "./ATNState.js"; - -export class BasicState extends ATNState { -} diff --git a/src/atn/BasicState.js b/src/atn/BasicState.ts similarity index 88% rename from src/atn/BasicState.js rename to src/atn/BasicState.ts index 3ce5302..9b00ca5 100644 --- a/src/atn/BasicState.js +++ b/src/atn/BasicState.ts @@ -8,7 +8,7 @@ import { ATNState } from "./ATNState.js"; import { ATNStateType } from "./ATNStateType.js"; export class BasicState extends ATNState { - get stateType() { + public override get stateType(): number { return ATNStateType.BASIC; } diff --git a/src/atn/BlockEndState.d.ts b/src/atn/BlockEndState.d.ts deleted file mode 100644 index 09bfccd..0000000 --- a/src/atn/BlockEndState.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; -import { BlockStartState } from "./BlockStartState.js"; - -/** Terminal node of a simple `(a|b|c)` block. */ -export class BlockEndState extends ATNState { - public startState: BlockStartState; -} diff --git a/src/atn/BlockEndState.js b/src/atn/BlockEndState.ts similarity index 73% rename from src/atn/BlockEndState.js rename to src/atn/BlockEndState.ts index f3ecda8..1f3090b 100644 --- a/src/atn/BlockEndState.js +++ b/src/atn/BlockEndState.ts @@ -6,12 +6,15 @@ import { ATNState } from "./ATNState.js"; import { ATNStateType } from "./ATNStateType.js"; +import { BlockStartState } from "./BlockStartState.js"; /** * Terminal node of a simple {@code (a|b|c)} block */ export class BlockEndState extends ATNState { - get stateType() { + public startState: BlockStartState | null = null; + + public override get stateType(): number { return ATNStateType.BLOCK_END; } diff --git a/src/atn/BlockStartState.d.ts b/src/atn/BlockStartState.d.ts deleted file mode 100644 index 720a6d7..0000000 --- a/src/atn/BlockStartState.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BlockEndState } from "./BlockEndState.js"; -import { DecisionState } from "./DecisionState.js"; - -/** The start of a regular `(...)` block. */ -export abstract class BlockStartState extends DecisionState { - public endState: BlockEndState; -} diff --git a/src/atn/BlockStartState.js b/src/atn/BlockStartState.ts similarity index 76% rename from src/atn/BlockStartState.js rename to src/atn/BlockStartState.ts index 93fe93f..af91dc6 100644 --- a/src/atn/BlockStartState.js +++ b/src/atn/BlockStartState.ts @@ -4,15 +4,17 @@ * can be found in the LICENSE.txt file in the project root. */ +import { BlockEndState } from "./BlockEndState.js"; import { DecisionState } from "./DecisionState.js"; /** * The start of a regular {@code (...)} block */ export class BlockStartState extends DecisionState { - constructor() { + public endState: BlockEndState | null; + + public constructor() { super(); this.endState = null; - return this; } } diff --git a/src/atn/DecisionState.js b/src/atn/DecisionState.js deleted file mode 100644 index 7b769ed..0000000 --- a/src/atn/DecisionState.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; - -export class DecisionState extends ATNState { - constructor() { - super(); - this.decision = -1; - this.nonGreedy = false; - return this; - } -} diff --git a/src/atn/DecisionState.d.ts b/src/atn/DecisionState.ts similarity index 82% rename from src/atn/DecisionState.d.ts rename to src/atn/DecisionState.ts index 6086a06..770416f 100644 --- a/src/atn/DecisionState.d.ts +++ b/src/atn/DecisionState.ts @@ -7,6 +7,6 @@ import { ATNState } from "./ATNState.js"; export class DecisionState extends ATNState { - public decision: number; - public nonGreedy: boolean; + public decision = -1; + public nonGreedy = false; } diff --git a/src/atn/EmptyPredictionContext.js b/src/atn/EmptyPredictionContext.js deleted file mode 100644 index 673db6d..0000000 --- a/src/atn/EmptyPredictionContext.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { PredictionContext } from "./PredictionContext.js"; -import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; - -export class EmptyPredictionContext extends SingletonPredictionContext { - - constructor() { - super(null, PredictionContext.EMPTY_RETURN_STATE); - } - - isEmpty() { - return true; - } - - getParent(index) { - return null; - } - - getReturnState(index) { - return this.returnState; - } - - equals(other) { - return this === other; - } - - toString() { - return "$"; - } -} - - -PredictionContext.EMPTY = new EmptyPredictionContext(); diff --git a/src/atn/EmptyPredictionContext.ts b/src/atn/EmptyPredictionContext.ts new file mode 100644 index 0000000..6aa76b3 --- /dev/null +++ b/src/atn/EmptyPredictionContext.ts @@ -0,0 +1,46 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { PredictionContext } from "./PredictionContext.js"; +import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; + +export class EmptyPredictionContext extends SingletonPredictionContext { + /** + * Represents {@code $} in local context prediction, which means wildcard. + * {@code *+x = *}. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly Instance = new EmptyPredictionContext(); + + public constructor() { + super(null, PredictionContext.EMPTY_RETURN_STATE); + } + + public override isEmpty(): boolean { + return true; + } + + public override getParent(_index: number): PredictionContext | null { + return null; + } + + public override getReturnState(_index: number): number { + return this.returnState; + } + + public override equals(other: unknown): boolean { + + return this === other; + } + + public override toString(): string { + return "$"; + } + + static { + PredictionContext.EMPTY = new EmptyPredictionContext(); + } +} diff --git a/src/atn/EpsilonTransition.d.ts b/src/atn/EpsilonTransition.d.ts deleted file mode 100644 index ed9ff52..0000000 --- a/src/atn/EpsilonTransition.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class EpsilonTransition extends Transition { - public outermostPrecedenceReturn: number; - - public constructor(target: ATNState, outermostPrecedenceReturn: number); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/EpsilonTransition.js b/src/atn/EpsilonTransition.js deleted file mode 100644 index 9a7efe3..0000000 --- a/src/atn/EpsilonTransition.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Transition } from "./Transition.js"; -import { TransitionType } from "./TransitionType.js"; - -export class EpsilonTransition extends Transition { - constructor(target, outermostPrecedenceReturn) { - super(target); - this.serializationType = TransitionType.EPSILON; - this.isEpsilon = true; - this.outermostPrecedenceReturn = outermostPrecedenceReturn; - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return false; - } - - toString() { - return "epsilon"; - } -} diff --git a/src/atn/EpsilonTransition.ts b/src/atn/EpsilonTransition.ts new file mode 100644 index 0000000..fe6d36f --- /dev/null +++ b/src/atn/EpsilonTransition.ts @@ -0,0 +1,46 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Transition } from "./Transition.js"; +import { TransitionType } from "./TransitionType.js"; +import { ATNState } from "./ATNState.js"; + +export class EpsilonTransition extends Transition { + readonly #outermostPrecedenceReturn: number; + + public constructor(target: ATNState, outermostPrecedenceReturn = -1) { + super(target); + this.#outermostPrecedenceReturn = outermostPrecedenceReturn; + } + + public override get isEpsilon(): boolean { + return true; + } + + public override matches(_symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return false; + } + + /** + * @returns the rule index of a precedence rule for which this transition is + * returning from, where the precedence value is 0; otherwise, -1. + * + * @see ATNConfig#isPrecedenceFilterSuppressed() + * @see ParserATNSimulator#applyPrecedenceFilter(ATNConfigSet) + * @since 4.4.1 + */ + public get outermostPrecedenceReturn(): number { + return this.#outermostPrecedenceReturn; + } + + public get serializationType(): number { + return TransitionType.EPSILON; + } + + public override toString(): string { + return "epsilon"; + } +} diff --git a/src/atn/LL1Analyzer.js b/src/atn/LL1Analyzer.ts similarity index 72% rename from src/atn/LL1Analyzer.js rename to src/atn/LL1Analyzer.ts index 78378a5..f5ed291 100644 --- a/src/atn/LL1Analyzer.js +++ b/src/atn/LL1Analyzer.ts @@ -4,22 +4,36 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from '../Token.js'; -import { ATNConfig } from './ATNConfig.js'; -import { IntervalSet } from '../misc/IntervalSet.js'; -import { RuleStopState } from './RuleStopState.js'; -import { RuleTransition } from './RuleTransition.js'; -import { NotSetTransition } from './NotSetTransition.js'; -import { WildcardTransition } from './WildcardTransition.js'; -import { AbstractPredicateTransition } from './AbstractPredicateTransition.js'; -import { predictionContextFromRuleContext } from './PredictionContextUtils.js'; -import { PredictionContext } from './PredictionContext.js'; -import { SingletonPredictionContext } from './SingletonPredictionContext.js'; +/* eslint-disable no-underscore-dangle */ + +import { Token } from "../Token.js"; +import { ATNConfig } from "./ATNConfig.js"; +import { IntervalSet } from "../misc/IntervalSet.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { RuleTransition } from "./RuleTransition.js"; +import { NotSetTransition } from "./NotSetTransition.js"; +import { WildcardTransition } from "./WildcardTransition.js"; +import { AbstractPredicateTransition } from "./AbstractPredicateTransition.js"; +import { predictionContextFromRuleContext } from "./PredictionContextUtils.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; import { BitSet } from "../misc/BitSet.js"; import { HashSet } from "../misc/HashSet.js"; +import { ATNState } from "./ATNState.js"; +import { ATN } from "./ATN.js"; +import { RuleContext } from "../RuleContext.js"; export class LL1Analyzer { - constructor(atn) { + /** + * Special value added to the lookahead sets to indicate that we hit + * a predicate during analysis if {@code seeThruPreds==false}. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly HIT_PRED = Token.INVALID_TYPE; + + public readonly atn: ATN; + + public constructor(atn: ATN) { this.atn = atn; } @@ -31,26 +45,29 @@ export class LL1Analyzer { * element at index i of the result will be {@code null}. * * @param s the ATN state - * @return the expected symbols for each outgoing transition of {@code s}. + * @returns the expected symbols for each outgoing transition of {@code s}. */ - getDecisionLookahead(s) { + public getDecisionLookahead(s: ATNState | null): Array | null { if (s === null) { return null; } + const count = s.transitions.length; - const look = []; + const look = new Array(count); + look.fill(null); for (let alt = 0; alt < count; alt++) { look[alt] = new IntervalSet(); - const lookBusy = new HashSet(); + const lookBusy = new HashSet(); const seeThruPreds = false; // fail to get lookahead upon pred - this._LOOK(s.transition(alt).target, null, PredictionContext.EMPTY, - look[alt], lookBusy, new BitSet(), seeThruPreds, false); + this._LOOK(s.transitions[alt].target, null, PredictionContext.EMPTY, + look[alt]!, lookBusy, new BitSet(), seeThruPreds, false); // Wipe out lookahead for this alternative if we found nothing // or we had a predicate when we !seeThruPreds - if (look[alt].length === 0 || look[alt].contains(LL1Analyzer.HIT_PRED)) { + if (look[alt]!.length === 0 || look[alt]!.contains(LL1Analyzer.HIT_PRED)) { look[alt] = null; } } + return look; } @@ -69,15 +86,17 @@ export class LL1Analyzer { * @param ctx the complete parser context, or {@code null} if the context * should be ignored * - * @return The set of tokens that can follow {@code s} in the ATN in the + * @returns The set of tokens that can follow {@code s} in the ATN in the * specified {@code ctx}. */ - LOOK(s, stopState, ctx) { + // eslint-disable-next-line @typescript-eslint/naming-convention + public LOOK(s: ATNState, stopState: ATNState | null, ctx: RuleContext | null): IntervalSet { const r = new IntervalSet(); const seeThruPreds = true; // ignore preds; get all lookahead ctx = ctx || null; - const lookContext = ctx !== null ? predictionContextFromRuleContext(s.atn, ctx) : null; + const lookContext = ctx !== null ? predictionContextFromRuleContext(s.atn!, ctx) : null; this._LOOK(s, stopState, lookContext, r, new HashSet(), new BitSet(), seeThruPreds, true); + return r; } @@ -105,13 +124,14 @@ export class LL1Analyzer { * {@code new BitSet()} for this argument. * @param seeThruPreds {@code true} to true semantic predicates as * implicitly {@code true} and "see through them", otherwise {@code false} - * to treat semantic predicates as opaque and add {@link //HIT_PRED} to the + * to treat semantic predicates as opaque and add {@link HIT_PRED} to the * result if one is encountered. * @param addEOF Add {@link Token//EOF} to the result if the end of the * outermost context is reached. This parameter has no effect if {@code ctx} * is {@code null}. */ - _LOOK(s, stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF) { + public _LOOK(s: ATNState, stopState: ATNState | null, ctx: PredictionContext | null, look: IntervalSet, + lookBusy: HashSet, calledRuleStack: BitSet, seeThruPreds: boolean, addEOF: boolean): void { const c = new ATNConfig({ state: s, alt: 0, context: ctx }, null); if (lookBusy.has(c)) { return; @@ -120,18 +140,22 @@ export class LL1Analyzer { if (s === stopState) { if (ctx === null) { look.addOne(Token.EPSILON); + return; } else if (ctx.isEmpty() && addEOF) { look.addOne(Token.EOF); + return; } } if (s instanceof RuleStopState) { if (ctx === null) { look.addOne(Token.EPSILON); + return; } else if (ctx.isEmpty() && addEOF) { look.addOne(Token.EOF); + return; } if (ctx !== PredictionContext.EMPTY) { @@ -140,20 +164,22 @@ export class LL1Analyzer { calledRuleStack.clear(s.ruleIndex); // run thru all possible stack tops in ctx for (let i = 0; i < ctx.length; i++) { - const returnState = this.atn.states[ctx.getReturnState(i)]; - this._LOOK(returnState, stopState, ctx.getParent(i), look, lookBusy, calledRuleStack, seeThruPreds, addEOF); + const returnState = this.atn.states[ctx.getReturnState(i)]!; + this._LOOK(returnState, stopState, ctx.getParent(i), look, lookBusy, calledRuleStack, + seeThruPreds, addEOF); } } finally { if (removed) { calledRuleStack.set(s.ruleIndex); } } + return; } } - for (let j = 0; j < s.transitions.length; j++) { - const t = s.transitions[j]; - if (t.constructor === RuleTransition) { + + for (const t of s.transitions) { + if (t instanceof RuleTransition) { if (calledRuleStack.get(t.target.ruleIndex)) { continue; } @@ -186,9 +212,3 @@ export class LL1Analyzer { } } } - -/** - * Special value added to the lookahead sets to indicate that we hit - * a predicate during analysis if {@code seeThruPreds==false}. - */ -LL1Analyzer.HIT_PRED = Token.INVALID_TYPE; diff --git a/src/atn/LexerATNConfig.js b/src/atn/LexerATNConfig.js deleted file mode 100644 index b889b39..0000000 --- a/src/atn/LexerATNConfig.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DecisionState } from "./DecisionState.js"; -import { ATNConfig } from "./ATNConfig.js"; - -export class LexerATNConfig extends ATNConfig { - constructor(params, config) { - super(params, config); - - // This is the backing field for {@link //getLexerActionExecutor}. - const lexerActionExecutor = params.lexerActionExecutor || null; - this.lexerActionExecutor = lexerActionExecutor || (config !== null ? config.lexerActionExecutor : null); - this.passedThroughNonGreedyDecision = config !== null ? this.checkNonGreedyDecision(config, this.state) : false; - this.hashCodeForConfigSet = LexerATNConfig.prototype.hashCode; - this.equalsForConfigSet = LexerATNConfig.prototype.equals; - return this; - } - - updateHashCode(hash) { - hash.update(this.state.stateNumber, this.alt, this.context, this.semanticContext, this.passedThroughNonGreedyDecision, this.lexerActionExecutor); - } - - equals(other) { - return this === other || - (other instanceof LexerATNConfig && - this.passedThroughNonGreedyDecision === other.passedThroughNonGreedyDecision && - (this.lexerActionExecutor ? this.lexerActionExecutor.equals(other.lexerActionExecutor) : !other.lexerActionExecutor) && - super.equals(other)); - } - - checkNonGreedyDecision(source, target) { - return source.passedThroughNonGreedyDecision || - (target instanceof DecisionState) && target.nonGreedy; - } -} diff --git a/src/atn/LexerATNConfig.ts b/src/atn/LexerATNConfig.ts new file mode 100644 index 0000000..55f9e1e --- /dev/null +++ b/src/atn/LexerATNConfig.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { DecisionState } from "./DecisionState.js"; +import { ATNConfig } from "./ATNConfig.js"; +import { LexerActionExecutor } from "./LexerActionExecutor.js"; +import { ATNState } from "./ATNState.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { HashCode } from "../misc/HashCode.js"; + +export interface ILexerATNConfigParameters { + state: ATNState | null, + alt?: number | null, + context?: PredictionContext | null, + lexerActionExecutor?: LexerActionExecutor | null, +}; + +export class LexerATNConfig extends ATNConfig { + /** + * This is the backing field for {@link #getLexerActionExecutor}. + */ + public readonly lexerActionExecutor: LexerActionExecutor | null; + + public readonly passedThroughNonGreedyDecision: boolean; + + public constructor(params: ILexerATNConfigParameters, config: LexerATNConfig | null) { + super(params, config); + + // This is the backing field for {@link getLexerActionExecutor}. + this.lexerActionExecutor = params.lexerActionExecutor ?? config?.lexerActionExecutor ?? null; + this.passedThroughNonGreedyDecision = config !== null ? this.checkNonGreedyDecision(config, this.state) : false; + + return this; + } + + public override updateHashCode(hash: HashCode): void { + hash.update(this.state.stateNumber, this.alt, this.context, this.semanticContext, + this.passedThroughNonGreedyDecision, this.lexerActionExecutor); + } + + public override equals(other: unknown): boolean { + return this === other || + (other instanceof LexerATNConfig && + this.passedThroughNonGreedyDecision === other.passedThroughNonGreedyDecision && + (this.lexerActionExecutor + ? this.lexerActionExecutor.equals(other.lexerActionExecutor) + : !other.lexerActionExecutor + ) && super.equals(other) + ); + } + + public override hashCodeForConfigSet(): number { + return this.hashCode(); + } + + public override equalsForConfigSet(other: unknown): boolean { + return this.equals(other); + } + + public checkNonGreedyDecision(source: LexerATNConfig, target: ATNState): boolean { + return source.passedThroughNonGreedyDecision || + ((target instanceof DecisionState) && target.nonGreedy); + } +} diff --git a/src/atn/LexerATNSimulator.d.ts b/src/atn/LexerATNSimulator.d.ts deleted file mode 100644 index 9fded87..0000000 --- a/src/atn/LexerATNSimulator.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNSimulator } from "./ATNSimulator.js"; -import { ATN } from "./ATN.js"; -import { PredictionContextCache } from "./PredictionContextCache.js"; -import { DFA } from "../dfa/DFA.js"; -import { CharStream } from "../CharStream.js"; -import { Lexer } from "../Lexer.js"; - -export declare class LexerATNSimulator extends ATNSimulator { - public decisionToDFA: DFA[]; - - public constructor(recog: Lexer, atn: ATN, decisionToDFA: DFA[], sharedContextCache: PredictionContextCache); - - public consume(input: CharStream): void; - -} diff --git a/src/atn/LexerATNSimulator.js b/src/atn/LexerATNSimulator.ts similarity index 73% rename from src/atn/LexerATNSimulator.js rename to src/atn/LexerATNSimulator.ts index e9966a8..48a4c6c 100644 --- a/src/atn/LexerATNSimulator.js +++ b/src/atn/LexerATNSimulator.ts @@ -4,38 +4,63 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from '../Token.js'; -import { Lexer } from './../Lexer.js'; -import { ATN } from './ATN.js'; -import { ATNSimulator } from './ATNSimulator.js'; -import { DFAState } from '../dfa/DFAState.js'; -import { OrderedATNConfigSet } from './OrderedATNConfigSet.js'; -import { PredictionContext } from './PredictionContext.js'; -import { SingletonPredictionContext } from './SingletonPredictionContext.js'; -import { RuleStopState } from './RuleStopState.js'; -import { LexerATNConfig } from './LexerATNConfig.js'; -import { LexerActionExecutor } from './LexerActionExecutor.js'; -import { LexerNoViableAltException } from '../LexerNoViableAltException.js'; +/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, max-classes-per-file */ +/* eslint-disable jsdoc/require-returns */ + +import { Token } from "../Token.js"; +import { Lexer } from "../Lexer.js"; +import { ATN } from "./ATN.js"; +import { ATNSimulator } from "./ATNSimulator.js"; +import { DFAState } from "../dfa/DFAState.js"; +import { OrderedATNConfigSet } from "./OrderedATNConfigSet.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { LexerATNConfig } from "./LexerATNConfig.js"; +import { LexerActionExecutor } from "./LexerActionExecutor.js"; +import { LexerNoViableAltException } from "../LexerNoViableAltException.js"; import { TransitionType } from "./TransitionType.js"; +import { DFA } from "../dfa/DFA.js"; +import { PredictionContextCache } from "./PredictionContextCache.js"; +import { CharStream } from "../CharStream.js"; +import { ATNConfigSet } from "./ATNConfigSet.js"; +import { Transition } from "./Transition.js"; +import { ATNState } from "./ATNState.js"; +import { RuleTransition } from "./RuleTransition.js"; +import { PredicateTransition } from "./PredicateTransition.js"; +import { ActionTransition } from "./ActionTransition.js"; -function resetSimState(sim) { - sim.index = -1; - sim.line = 0; - sim.column = -1; - sim.dfaState = null; -} +export class LexerATNSimulator extends ATNSimulator { + public static readonly MIN_DFA_EDGE = 0; + public static readonly MAX_DFA_EDGE = 127; // forces unicode to stay in ATN -class SimState { - constructor() { - resetSimState(this); - } + public static debug = false; - reset() { - resetSimState(this); - } -} + private static dfa_debug = false; + + public readonly decisionToDFA: DFA[]; + + public readonly recog: Lexer | null = null; + + /** + * The current token's starting index into the character stream. + * Shared across DFA to ATN simulation in case the ATN fails and the + * DFA did not have a previous accept state. In this case, we use the + * ATN-generated exception object. + */ + public startIndex = -1; + + /** line number 1..n within the input */ + public line = 1; + + /** The index of the character relative to the beginning of the line 0..n-1 */ + public column = 0; + + public mode: number = Lexer.DEFAULT_MODE; + + /** Used during DFA/ATN exec to record the most recent accept configuration info */ + protected readonly prevAccept = new LexerATNSimulator.SimState(); -export class LexerATNSimulator extends ATNSimulator { /** * When we hit an accept state in either the DFA or the ATN, we * have to notify the character stream to start buffering characters @@ -52,40 +77,21 @@ export class LexerATNSimulator extends ATNSimulator { * then the ATN does the accept and the DFA simulator that invoked it * can simply return the predicted token type.

            */ - constructor(recog, atn, decisionToDFA, sharedContextCache) { + public constructor(recog: Lexer | null, atn: ATN, decisionToDFA: DFA[], + sharedContextCache: PredictionContextCache | null) { super(atn, sharedContextCache); this.decisionToDFA = decisionToDFA; this.recog = recog; - /** - * The current token's starting index into the character stream. - * Shared across DFA to ATN simulation in case the ATN fails and the - * DFA did not have a previous accept state. In this case, we use the - * ATN-generated exception object - */ - this.startIndex = -1; - // line number 1..n within the input/// - this.line = 1; - /** - * The index of the character relative to the beginning of the line - * 0..n-1 - */ - this.column = 0; - this.mode = Lexer.DEFAULT_MODE; - /** - * Used during DFA/ATN exec to record the most recent accept configuration - * info - */ - this.prevAccept = new SimState(); } - copyState(simulator) { + public copyState(simulator: LexerATNSimulator): void { this.column = simulator.column; this.line = simulator.line; this.mode = simulator.mode; this.startIndex = simulator.startIndex; } - match(input, mode) { + public match(input: CharStream, mode: number): number { this.mode = mode; const mark = input.mark(); try { @@ -102,7 +108,7 @@ export class LexerATNSimulator extends ATNSimulator { } } - reset() { + public reset(): void { this.prevAccept.reset(); this.startIndex = -1; this.line = 1; @@ -110,14 +116,43 @@ export class LexerATNSimulator extends ATNSimulator { this.mode = Lexer.DEFAULT_MODE; } - matchATN(input) { + public getDFA(mode: number): DFA { + return this.decisionToDFA[mode]; + } + + // Get the text matched so far for the current token. + public getText(input: CharStream): string { + // index is first lookahead char, don't include. + return input.getText(this.startIndex, input.index - 1); + } + + public consume(input: CharStream): void { + const curChar = input.LA(1); + if (curChar === "\n".charCodeAt(0)) { + this.line += 1; + this.column = 0; + } else { + this.column += 1; + } + input.consume(); + } + + public getTokenName(tt: number): string { + if (tt === -1) { + return "EOF"; + } else { + return "'" + String.fromCharCode(tt) + "'"; + } + } + + protected matchATN(input: CharStream): number { const startState = this.atn.modeToStartState[this.mode]; if (LexerATNSimulator.debug) { console.log("matchATN mode " + this.mode + " start: " + startState); } const old_mode = this.mode; - const s0_closure = this.computeStartState(input, startState); + const s0_closure = this.computeStartState(input, startState!); const suppressEdge = s0_closure.hasSemanticContext; s0_closure.hasSemanticContext = false; @@ -131,10 +166,11 @@ export class LexerATNSimulator extends ATNSimulator { if (LexerATNSimulator.debug) { console.log("DFA after matchATN: " + this.decisionToDFA[old_mode].toLexerString()); } + return predict; } - execATN(input, ds0) { + protected execATN(input: CharStream, ds0: DFAState): number { if (LexerATNSimulator.debug) { console.log("start state closure=" + ds0.configs); } @@ -176,9 +212,11 @@ export class LexerATNSimulator extends ATNSimulator { target = this.computeTargetState(input, s, t); // print("Computed:" + str(target)) } + if (target === ATNSimulator.ERROR) { break; } + // If this is a consumable input element, make sure to consume before // capturing the accept state so the input index, line, and char // position accurately reflect the state of the interpreter at the @@ -186,6 +224,7 @@ export class LexerATNSimulator extends ATNSimulator { if (t !== Token.EOF) { this.consume(input); } + if (target.isAcceptState) { this.captureSimState(this.prevAccept, input, target); if (t === Token.EOF) { @@ -193,8 +232,9 @@ export class LexerATNSimulator extends ATNSimulator { } } t = input.LA(1); - s = target; // flip; current DFA target becomes new src/from state + s = target!; // flip; current DFA target becomes new src/from state } + return this.failOrAccept(this.prevAccept, input, s.configs, t); } @@ -205,11 +245,11 @@ export class LexerATNSimulator extends ATNSimulator { * * @param s The current DFA state * @param t The next input symbol - * @return The existing target DFA state for the given input symbol + * @returns The existing target DFA state for the given input symbol * {@code t}, or {@code null} if the target state for this edge is not * already cached */ - getExistingTargetState(s, t) { + protected getExistingTargetState(s: DFAState, t: number): DFAState | null { if (s.edges === null || t < LexerATNSimulator.MIN_DFA_EDGE || t > LexerATNSimulator.MAX_DFA_EDGE) { return null; } @@ -221,6 +261,7 @@ export class LexerATNSimulator extends ATNSimulator { if (LexerATNSimulator.debug && target !== null) { console.log("reuse state " + s.stateNumber + " edge to " + target.stateNumber); } + return target; } @@ -232,11 +273,11 @@ export class LexerATNSimulator extends ATNSimulator { * @param s The current DFA state * @param t The next input symbol * - * @return The computed target DFA state for the given input symbol + * @returns The computed target DFA state for the given input symbol * {@code t}. If {@code t} does not lead to a valid DFA state, this method - * returns {@link //ERROR}. + * returns {@link ERROR}. */ - computeTargetState(input, s, t) { + protected computeTargetState(input: CharStream, s: DFAState, t: number): DFAState { const reach = new OrderedATNConfigSet(); // if we don't find an existing DFA state // Fill reach starting from closure, following t transitions @@ -248,25 +289,29 @@ export class LexerATNSimulator extends ATNSimulator { // cause a failover from DFA later. this.addDFAEdge(s, t, ATNSimulator.ERROR); } + // stop when we can't match any more char return ATNSimulator.ERROR; } + // Add an edge from s to target DFA found/created for reach return this.addDFAEdge(s, t, null, reach); } - failOrAccept(prevAccept, input, reach, t) { - if (this.prevAccept.dfaState !== null) { + protected failOrAccept(prevAccept: LexerATNSimulator.SimState, input: CharStream, reach: ATNConfigSet, + t: number): number { + if (prevAccept.dfaState !== null) { const lexerActionExecutor = prevAccept.dfaState.lexerActionExecutor; this.accept(input, lexerActionExecutor, this.startIndex, prevAccept.index, prevAccept.line, prevAccept.column); + return prevAccept.dfaState.prediction; } else { // if no accept and EOF is first char, return EOF if (t === Token.EOF && input.index === this.startIndex) { return Token.EOF; } - throw new LexerNoViableAltException(this.recog, input, this.startIndex, reach); + throw new LexerNoViableAltException(this.recog!, input, this.startIndex, reach); } } @@ -275,30 +320,29 @@ export class LexerATNSimulator extends ATNSimulator { * we can reach upon input {@code t}. Parameter {@code reach} is a return * parameter. */ - getReachableConfigSet(input, closure, reach, t) { + protected getReachableConfigSet(input: CharStream, closure: ATNConfigSet, reach: ATNConfigSet, t: number): void { // 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 (let i = 0; i < closure.items.length; i++) { - const cfg = closure.items[i]; + for (const cfg of closure.items) { const currentAltReachedAcceptState = (cfg.alt === skipAlt); - if (currentAltReachedAcceptState && cfg.passedThroughNonGreedyDecision) { + if (currentAltReachedAcceptState && (cfg as LexerATNConfig).passedThroughNonGreedyDecision) { continue; } + if (LexerATNSimulator.debug) { - console.log("testing %s at %s\n", this.getTokenName(t), cfg - .toString(this.recog, true)); + console.log("testing %s at %s\n", this.getTokenName(t), cfg.toString(this.recog, true)); } - for (let j = 0; j < cfg.state.transitions.length; j++) { - const trans = cfg.state.transitions[j]; // for each transition + + for (const trans of cfg.state.transitions) { const target = this.getReachableTarget(trans, t); if (target !== null) { - let lexerActionExecutor = cfg.lexerActionExecutor; + let lexerActionExecutor = (cfg as LexerATNConfig).lexerActionExecutor; if (lexerActionExecutor !== null) { lexerActionExecutor = lexerActionExecutor.fixOffsetBeforeMatch(input.index - this.startIndex); } const treatEofAsEpsilon = (t === Token.EOF); - const config = new LexerATNConfig({ state: target, lexerActionExecutor: lexerActionExecutor }, cfg); + const config = new LexerATNConfig({ state: target, lexerActionExecutor }, (cfg as LexerATNConfig)); if (this.closure(input, config, reach, currentAltReachedAcceptState, true, treatEofAsEpsilon)) { // any remaining configs for this alt have a lower priority @@ -310,7 +354,8 @@ export class LexerATNSimulator extends ATNSimulator { } } - accept(input, lexerActionExecutor, startIndex, index, line, charPos) { + protected accept(input: CharStream, lexerActionExecutor: LexerActionExecutor | null, startIndex: number, + index: number, line: number, charPos: number): void { if (LexerATNSimulator.debug) { console.log("ACTION %s\n", lexerActionExecutor); } @@ -323,7 +368,7 @@ export class LexerATNSimulator extends ATNSimulator { } } - getReachableTarget(trans, t) { + protected getReachableTarget(trans: Transition, t: number): ATNState | null { if (trans.matches(t, 0, Lexer.MAX_CHAR_VALUE)) { return trans.target; } else { @@ -331,7 +376,7 @@ export class LexerATNSimulator extends ATNSimulator { } } - computeStartState(input, p) { + protected computeStartState(input: CharStream, p: ATNState): ATNConfigSet { const initialContext = PredictionContext.EMPTY; const configs = new OrderedATNConfigSet(); for (let i = 0; i < p.transitions.length; i++) { @@ -339,6 +384,7 @@ export class LexerATNSimulator extends ATNSimulator { const cfg = new LexerATNConfig({ state: target, alt: i + 1, context: initialContext }, null); this.closure(input, cfg, configs, false, false, false); } + return configs; } @@ -349,11 +395,11 @@ export class LexerATNSimulator extends ATNSimulator { * search from {@code config}, all other (potentially reachable) states for * this rule would have a lower priority. * - * @return {Boolean} {@code true} if an accept state is reached, otherwise + * @returns {boolean} {@code true} if an accept state is reached, otherwise * {@code false}. */ - closure(input, config, configs, - currentAltReachedAcceptState, speculative, treatEofAsEpsilon) { + protected closure(input: CharStream, config: LexerATNConfig, configs: ATNConfigSet, + currentAltReachedAcceptState: boolean, speculative: boolean, treatEofAsEpsilon: boolean): boolean { let cfg = null; if (LexerATNSimulator.debug) { console.log("closure(" + config.toString(this.recog, true) + ")"); @@ -369,6 +415,7 @@ export class LexerATNSimulator extends ATNSimulator { if (config.context === null || config.context.hasEmptyPath()) { if (config.context === null || config.context.isEmpty()) { configs.add(config); + return true; } else { configs.add(new LexerATNConfig({ state: config.state, context: PredictionContext.EMPTY }, config)); @@ -387,34 +434,38 @@ export class LexerATNSimulator extends ATNSimulator { } } } + return currentAltReachedAcceptState; } + // optimization if (!config.state.epsilonOnlyTransitions) { if (!currentAltReachedAcceptState || !config.passedThroughNonGreedyDecision) { configs.add(config); } } - for (let j = 0; j < config.state.transitions.length; j++) { - const trans = config.state.transitions[j]; + + for (const trans of config.state.transitions) { cfg = this.getEpsilonTarget(input, config, trans, configs, speculative, treatEofAsEpsilon); if (cfg !== null) { currentAltReachedAcceptState = this.closure(input, cfg, configs, currentAltReachedAcceptState, speculative, treatEofAsEpsilon); } } + return currentAltReachedAcceptState; } // side-effect: can alter configs.hasSemanticContext - getEpsilonTarget(input, config, trans, - configs, speculative, treatEofAsEpsilon) { + protected getEpsilonTarget(input: CharStream, config: LexerATNConfig, trans: Transition, configs: ATNConfigSet, + speculative: boolean, treatEofAsEpsilon: boolean): LexerATNConfig | null { let cfg = null; if (trans.serializationType === TransitionType.RULE) { - const newContext = SingletonPredictionContext.create(config.context, trans.followState.stateNumber); + const newContext = SingletonPredictionContext.create(config.context, + (trans as RuleTransition).followState.stateNumber); cfg = new LexerATNConfig({ state: trans.target, context: newContext }, config); } else if (trans.serializationType === TransitionType.PRECEDENCE) { - throw "Precedence predicates are not supported in lexers."; + throw new Error("Precedence predicates are not supported in lexers."); } else if (trans.serializationType === TransitionType.PREDICATE) { // Track traversing semantic predicates. If we traverse, // we cannot add a DFA state for this "reach" computation @@ -434,11 +485,12 @@ export class LexerATNSimulator extends ATNSimulator { // states reached by traversing predicates. Since this is when we // test them, we cannot cash the DFA state target of ID. + const pt = trans as PredicateTransition; if (LexerATNSimulator.debug) { - console.log("EVAL rule " + trans.ruleIndex + ":" + trans.predIndex); + console.log("EVAL rule " + pt.ruleIndex + ":" + pt.predIndex); } configs.hasSemanticContext = true; - if (this.evaluatePredicate(input, trans.ruleIndex, trans.predIndex, speculative)) { + if (this.evaluatePredicate(input, pt.ruleIndex, pt.predIndex, speculative)) { cfg = new LexerATNConfig({ state: trans.target }, config); } } else if (trans.serializationType === TransitionType.ACTION) { @@ -455,9 +507,9 @@ export class LexerATNSimulator extends ATNSimulator { // getEpsilonTarget to return two configurations, so // additional modifications are needed before we can support // the split operation. - const lexerActionExecutor = LexerActionExecutor.append(config.lexerActionExecutor, - this.atn.lexerActions[trans.actionIndex]); - cfg = new LexerATNConfig({ state: trans.target, lexerActionExecutor: lexerActionExecutor }, config); + const lexerActionExecutor = LexerActionExecutor.append(config.lexerActionExecutor!, + this.atn.lexerActions[(trans as ActionTransition).actionIndex]); + cfg = new LexerATNConfig({ state: trans.target, lexerActionExecutor }, config); } else { // ignore actions in referenced rules cfg = new LexerATNConfig({ state: trans.target }, config); @@ -473,20 +525,21 @@ export class LexerATNSimulator extends ATNSimulator { } } } + return cfg; - } + }; /** * Evaluate a predicate specified in the lexer. * *

            If {@code speculative} is {@code true}, this method was called before - * {@link //consume} for the matched character. This method should call - * {@link //consume} before evaluating the predicate to ensure position + * {@link consume} for the matched character. This method should call + * {@link consume} before evaluating the predicate to ensure position * sensitive values, including {@link Lexer//getText}, {@link Lexer//getLine}, * and {@link Lexer}, properly reflect the current * lexer state. This method should restore {@code input} and the simulator * to the original state before returning (i.e. undo the actions made by the - * call to {@link //consume}.

            + * call to {@link consume}.

            * * @param input The input stream. * @param ruleIndex The rule containing the predicate. @@ -494,11 +547,11 @@ export class LexerATNSimulator extends ATNSimulator { * @param speculative {@code true} if the current index in {@code input} is * one character before the predicate's location. * - * @return {@code true} if the specified predicate evaluates to + * @returns `true` if the specified predicate evaluates to * {@code true}. */ - evaluatePredicate(input, ruleIndex, - predIndex, speculative) { + protected evaluatePredicate(input: CharStream, ruleIndex: number, predIndex: number, + speculative: boolean): boolean { // assume true if no recognizer was provided if (this.recog === null) { return true; @@ -512,6 +565,7 @@ export class LexerATNSimulator extends ATNSimulator { const marker = input.mark(); try { this.consume(input); + return this.recog.sempred(null, ruleIndex, predIndex); } finally { this.column = savedColumn; @@ -521,14 +575,15 @@ export class LexerATNSimulator extends ATNSimulator { } } - captureSimState(settings, input, dfaState) { + protected captureSimState(settings: LexerATNSimulator.SimState, input: CharStream, + dfaState: DFAState | null): void { settings.index = input.index; settings.line = this.line; settings.column = this.column; settings.dfaState = dfaState; } - addDFAEdge(from_, tk, to, configs) { + protected addDFAEdge(from_: DFAState, tk: number, to: DFAState | null, configs?: ATNConfigSet | null): DFAState { if (to === undefined) { to = null; } @@ -558,21 +613,26 @@ export class LexerATNSimulator extends ATNSimulator { return to; } } + // add the edge if (tk < LexerATNSimulator.MIN_DFA_EDGE || tk > LexerATNSimulator.MAX_DFA_EDGE) { // Only track edges within the DFA bounds - return to; + return to!; } + if (LexerATNSimulator.debug) { console.log("EDGE " + from_ + " -> " + to + " upon " + tk); } + if (from_.edges === null) { // make room for tokens 1..n and -1 masquerading as index 0 - from_.edges = []; + from_.edges = new Array(LexerATNSimulator.MAX_DFA_EDGE - LexerATNSimulator.MIN_DFA_EDGE + 1); + from_.edges.fill(null); } + from_.edges[tk - LexerATNSimulator.MIN_DFA_EDGE] = to; // connect - return to; + return to!; } /** @@ -581,66 +641,67 @@ export class LexerATNSimulator extends ATNSimulator { * configuration containing an ATN rule stop state. Later, when * traversing the DFA, we will know which rule to accept. */ - addDFAState(configs) { - const proposed = new DFAState(null, configs); + protected addDFAState(configs: ATNConfigSet): DFAState { + const proposed = new DFAState(configs); let firstConfigWithRuleStopState = null; - for (let i = 0; i < configs.items.length; i++) { - const cfg = configs.items[i]; + for (const cfg of configs.items) { if (cfg.state instanceof RuleStopState) { firstConfigWithRuleStopState = cfg; break; } } + if (firstConfigWithRuleStopState !== null) { proposed.isAcceptState = true; - proposed.lexerActionExecutor = firstConfigWithRuleStopState.lexerActionExecutor; + proposed.lexerActionExecutor = (firstConfigWithRuleStopState as LexerATNConfig).lexerActionExecutor; proposed.prediction = this.atn.ruleToTokenType[firstConfigWithRuleStopState.state.ruleIndex]; } + const dfa = this.decisionToDFA[this.mode]; const existing = dfa.states.get(proposed); if (existing !== null) { return existing; } + const newState = proposed; newState.stateNumber = dfa.states.length; configs.setReadonly(true); newState.configs = configs; dfa.states.add(newState); - return newState; - } - - getDFA(mode) { - return this.decisionToDFA[mode]; - } - // Get the text matched so far for the current token. - getText(input) { - // index is first lookahead char, don't include. - return input.getText(this.startIndex, input.index - 1); - } - - consume(input) { - const curChar = input.LA(1); - if (curChar === "\n".charCodeAt(0)) { - this.line += 1; - this.column = 0; - } else { - this.column += 1; - } - input.consume(); + return newState; } +} - getTokenName(tt) { - if (tt === -1) { - return "EOF"; - } else { - return "'" + String.fromCharCode(tt) + "'"; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace LexerATNSimulator { + /** + * When we hit an accept state in either the DFA or the ATN, we + * have to notify the character stream to start buffering characters + * via {@link IntStream#mark} and record the current state. The current sim state + * includes the current index into the input, the current line, + * and current character position in that line. Note that the Lexer is + * tracking the starting line and characterization of the token. These + * variables track the "state" of the simulator when it hits an accept state. + * + *

            We track these variables separately for the DFA and ATN simulation + * because the DFA simulation often has to fail over to the ATN + * simulation. If the ATN simulation fails, we need the DFA to fall + * back to its previously accepted state, if any. If the ATN succeeds, + * then the ATN does the accept and the DFA simulator that invoked it + * can simply return the predicted token type.

            + */ + export class SimState { + public index = -1; + public line = 0; + public column = -1; + public dfaState: DFAState | null = null; + + public reset(): void { + this.index = -1; + this.line = 0; + this.column = -1; + this.dfaState = null; } } } - -LexerATNSimulator.debug = false; -LexerATNSimulator.dfa_debug = false; - -LexerATNSimulator.MIN_DFA_EDGE = 0; -LexerATNSimulator.MAX_DFA_EDGE = 127; // forces unicode to stay in ATN diff --git a/src/atn/LexerAction.d.ts b/src/atn/LexerAction.d.ts deleted file mode 100644 index f4304d9..0000000 --- a/src/atn/LexerAction.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class LexerAction { - public readonly actionType: number; - public readonly isPositionDependent: boolean; - - public constructor(actionType: number); - - public hashCode(): number; - public equals(obj: unknown): boolean; -} diff --git a/src/atn/LexerAction.js b/src/atn/LexerAction.ts similarity index 55% rename from src/atn/LexerAction.js rename to src/atn/LexerAction.ts index 636da1e..bc2aad4 100644 --- a/src/atn/LexerAction.js +++ b/src/atn/LexerAction.ts @@ -4,25 +4,33 @@ * can be found in the LICENSE.txt file in the project root. */ +import { Lexer } from "../Lexer.js"; import { HashCode } from "../misc/HashCode.js"; -export class LexerAction { - constructor(action) { +// TODO: make LexerAction an interface +export abstract class LexerAction { + public readonly actionType: number; + public isPositionDependent: boolean; + + public constructor(action: number) { this.actionType = action; this.isPositionDependent = false; } - hashCode() { + public hashCode(): number { const hash = new HashCode(); this.updateHashCode(hash); + return hash.finish(); } - updateHashCode(hash) { + public updateHashCode(hash: HashCode): void { hash.update(this.actionType); } - equals(other) { + public equals(other: unknown): boolean { return this === other; } + + public abstract execute(lexer: Lexer): void; } diff --git a/src/atn/LexerActionExecutor.js b/src/atn/LexerActionExecutor.ts similarity index 80% rename from src/atn/LexerActionExecutor.js rename to src/atn/LexerActionExecutor.ts index 28b42ba..be3436a 100644 --- a/src/atn/LexerActionExecutor.js +++ b/src/atn/LexerActionExecutor.ts @@ -4,10 +4,18 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerIndexedCustomAction } from './LexerIndexedCustomAction.js'; +/* eslint-disable jsdoc/require-param */ + +import { LexerIndexedCustomAction } from "./LexerIndexedCustomAction.js"; import { HashCode } from "../misc/HashCode.js"; +import { LexerAction } from "./LexerAction.js"; +import { CharStream } from "../CharStream.js"; +import { Lexer } from "../Lexer.js"; + +export class LexerActionExecutor /*implements*/ extends LexerAction { + private lexerActions: LexerAction[]; + private cachedHashCode: number; -export class LexerActionExecutor { /** * Represents an executor for a sequence of lexer actions which traversed during * the matching operation of a lexer rule (token). @@ -16,17 +24,44 @@ export class LexerActionExecutor { * efficiently, ensuring that actions appearing only at the end of the rule do * not cause bloating of the {@link DFA} created for the lexer.

            */ - constructor(lexerActions) { + public constructor(lexerActions: LexerAction[]) { + super(-1); + this.lexerActions = lexerActions === null ? [] : lexerActions; /** - * Caches the result of {@link //hashCode} since the hash code is an element + * Caches the result of {@link hashCode} since the hash code is an element * of the performance-critical {@link LexerATNConfig//hashCode} operation */ this.cachedHashCode = HashCode.hashStuff(lexerActions); // "".join([str(la) for la in + // lexerActions])) return this; } + /** + * Creates a {@link LexerActionExecutor} which executes the actions for + * the input {@code lexerActionExecutor} followed by a specified + * {@code lexerAction}. + * + * @param lexerActionExecutor The executor for actions already traversed by + * the lexer while matching a token within a particular + * {@link LexerATNConfig}. If this is {@code null}, the method behaves as + * though it were an empty executor. + * @param lexerAction The lexer action to execute after the actions + * specified in {@code lexerActionExecutor}. + * + * @returns {LexerActionExecutor} A {@link LexerActionExecutor} for executing the combine actions + * of {@code lexerActionExecutor} and {@code lexerAction}. + */ + public static append(lexerActionExecutor: LexerActionExecutor, lexerAction: LexerAction): LexerActionExecutor { + if (lexerActionExecutor === null) { + return new LexerActionExecutor([lexerAction]); + } + const lexerActions = lexerActionExecutor.lexerActions.concat([lexerAction]); + + return new LexerActionExecutor(lexerActions); + } + /** * Creates a {@link LexerActionExecutor} which encodes the current offset * for position-dependent lexer actions. @@ -53,10 +88,10 @@ export class LexerActionExecutor { * @param offset The current offset to assign to all position-dependent * lexer actions which do not already have offsets assigned. * - * @return {LexerActionExecutor} A {@link LexerActionExecutor} which stores input stream offsets + * @returns {LexerActionExecutor} A {@link LexerActionExecutor} which stores input stream offsets * for all position-dependent lexer actions. */ - fixOffsetBeforeMatch(offset) { + public fixOffsetBeforeMatch(offset: number): LexerActionExecutor { let updatedLexerActions = null; for (let i = 0; i < this.lexerActions.length; i++) { if (this.lexerActions[i].isPositionDependent && @@ -94,22 +129,26 @@ export class LexerActionExecutor { * {@link IntStream//seek} to set the {@code input} position to the beginning * of the token. */ - execute(lexer, input, startIndex) { + public execute(lexer: Lexer, input?: CharStream, startIndex?: number): void { + if (input === undefined || startIndex === undefined) { + return; + } + let requiresSeek = false; const stopIndex = input.index; try { - for (let i = 0; i < this.lexerActions.length; i++) { - let lexerAction = this.lexerActions[i]; + for (const lexerAction of this.lexerActions) { + let action = lexerAction; if (lexerAction instanceof LexerIndexedCustomAction) { const offset = lexerAction.offset; input.seek(startIndex + offset); - lexerAction = lexerAction.action; + action = lexerAction.action; requiresSeek = (startIndex + offset) !== stopIndex; } else if (lexerAction.isPositionDependent) { input.seek(stopIndex); requiresSeek = false; } - lexerAction.execute(lexer); + action.execute(lexer); } } finally { if (requiresSeek) { @@ -118,22 +157,18 @@ export class LexerActionExecutor { } } - hashCode() { + public override hashCode(): number { return this.cachedHashCode; } - updateHashCode(hash) { - hash.update(this.cachedHashCode); - } - - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerActionExecutor)) { return false; - } else if (this.cachedHashCode != other.cachedHashCode) { + } else if (this.cachedHashCode !== other.cachedHashCode) { return false; - } else if (this.lexerActions.length != other.lexerActions.length) { + } else if (this.lexerActions.length !== other.lexerActions.length) { return false; } else { const numActions = this.lexerActions.length; @@ -142,30 +177,9 @@ export class LexerActionExecutor { return false; } } + return true; } } - /** - * Creates a {@link LexerActionExecutor} which executes the actions for - * the input {@code lexerActionExecutor} followed by a specified - * {@code lexerAction}. - * - * @param lexerActionExecutor The executor for actions already traversed by - * the lexer while matching a token within a particular - * {@link LexerATNConfig}. If this is {@code null}, the method behaves as - * though it were an empty executor. - * @param lexerAction The lexer action to execute after the actions - * specified in {@code lexerActionExecutor}. - * - * @return {LexerActionExecutor} A {@link LexerActionExecutor} for executing the combine actions - * of {@code lexerActionExecutor} and {@code lexerAction}. - */ - static append(lexerActionExecutor, lexerAction) { - if (lexerActionExecutor === null) { - return new LexerActionExecutor([lexerAction]); - } - const lexerActions = lexerActionExecutor.lexerActions.concat([lexerAction]); - return new LexerActionExecutor(lexerActions); - } } diff --git a/src/atn/LexerActionType.js b/src/atn/LexerActionType.ts similarity index 90% rename from src/atn/LexerActionType.js rename to src/atn/LexerActionType.ts index f683920..fff08b5 100644 --- a/src/atn/LexerActionType.js +++ b/src/atn/LexerActionType.ts @@ -4,6 +4,8 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable @typescript-eslint/naming-convention */ + export const LexerActionType = { // The type of a {@link LexerChannelAction} action. CHANNEL: 0, @@ -20,5 +22,5 @@ export const LexerActionType = { //The type of a {@link LexerSkipAction} action. SKIP: 6, //The type of a {@link LexerTypeAction} action. - TYPE: 7 -}; + TYPE: 7, +} as const; diff --git a/src/atn/LexerChannelAction.js b/src/atn/LexerChannelAction.ts similarity index 69% rename from src/atn/LexerChannelAction.js rename to src/atn/LexerChannelAction.ts index 8e51c52..ee16312 100644 --- a/src/atn/LexerChannelAction.js +++ b/src/atn/LexerChannelAction.ts @@ -4,34 +4,42 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable jsdoc/require-param */ + import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; /** * Implements the {@code channel} lexer action by calling * {@link Lexer//setChannel} with the assigned channel. * Constructs a new {@code channel} action with the specified channel value. + * * @param channel The channel value to pass to {@link Lexer//setChannel} */ export class LexerChannelAction extends LexerAction { - constructor(channel) { + public readonly channel: number; + + public constructor(channel: number) { super(LexerActionType.CHANNEL); this.channel = channel; } /** *

            This action is implemented by calling {@link Lexer//setChannel} with the - * value provided by {@link //getChannel}.

            + * value provided by {@link getChannel}.

            */ - execute(lexer) { + public execute(lexer: Lexer): void { + // eslint-disable-next-line no-underscore-dangle lexer._channel = this.channel; } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.channel); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerChannelAction)) { @@ -41,7 +49,7 @@ export class LexerChannelAction extends LexerAction { } } - toString() { + public override toString(): string { return "channel(" + this.channel + ")"; } } diff --git a/src/atn/LexerCustomAction.js b/src/atn/LexerCustomAction.ts similarity index 79% rename from src/atn/LexerCustomAction.js rename to src/atn/LexerCustomAction.ts index babcf3f..6feae99 100644 --- a/src/atn/LexerCustomAction.js +++ b/src/atn/LexerCustomAction.ts @@ -4,8 +4,12 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +/* eslint-disable jsdoc/require-param */ + +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; /** * Executes a custom lexer action by calling {@link Recognizer//action} with the @@ -18,6 +22,9 @@ import { LexerAction } from "./LexerAction.js"; * command argument could not be evaluated when the grammar was compiled.

            */ export class LexerCustomAction extends LexerAction { + private ruleIndex: number; + private actionIndex: number; + /** * Constructs a custom lexer action with the specified rule and action * indexes. @@ -27,7 +34,7 @@ export class LexerCustomAction extends LexerAction { * @param actionIndex The action index to use for calls to * {@link Recognizer//action}. */ - constructor(ruleIndex, actionIndex) { + public constructor(ruleIndex: number, actionIndex: number) { super(LexerActionType.CUSTOM); this.ruleIndex = ruleIndex; this.actionIndex = actionIndex; @@ -38,15 +45,15 @@ export class LexerCustomAction extends LexerAction { *

            Custom actions are implemented by calling {@link Lexer//action} with the * appropriate rule and action indexes.

            */ - execute(lexer) { + public override execute(lexer: Lexer): void { lexer.action(null, this.ruleIndex, this.actionIndex); } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.ruleIndex, this.actionIndex); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerCustomAction)) { diff --git a/src/atn/LexerIndexedCustomAction.js b/src/atn/LexerIndexedCustomAction.ts similarity index 80% rename from src/atn/LexerIndexedCustomAction.js rename to src/atn/LexerIndexedCustomAction.ts index 263d062..0933d7d 100644 --- a/src/atn/LexerIndexedCustomAction.js +++ b/src/atn/LexerIndexedCustomAction.ts @@ -4,6 +4,12 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable jsdoc/require-param */ + +import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; + /** * This implementation of {@link LexerAction} is used for tracking input offsets * for position-dependent actions within a {@link LexerActionExecutor}. @@ -27,10 +33,11 @@ * input {@link CharStream}. */ -import { LexerAction } from "./LexerAction.js"; - export class LexerIndexedCustomAction extends LexerAction { - constructor(offset, action) { + public readonly offset: number; + public readonly action: LexerAction; + + public constructor(offset: number, action: LexerAction) { super(action.actionType); this.offset = offset; this.action = action; @@ -38,19 +45,19 @@ export class LexerIndexedCustomAction extends LexerAction { } /** - *

            This method calls {@link //execute} on the result of {@link //getAction} + *

            This method calls {@link execute} on the result of {@link getAction} * using the provided {@code lexer}.

            */ - execute(lexer) { + public execute(lexer: Lexer): void { // assume the input stream position was properly set by the calling code this.action.execute(lexer); } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.offset, this.action); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerIndexedCustomAction)) { diff --git a/src/atn/LexerModeAction.js b/src/atn/LexerModeAction.ts similarity index 64% rename from src/atn/LexerModeAction.js rename to src/atn/LexerModeAction.ts index a0318d3..0a1f437 100644 --- a/src/atn/LexerModeAction.js +++ b/src/atn/LexerModeAction.ts @@ -4,32 +4,38 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +/* eslint-disable jsdoc/require-param */ + +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; /** * Implements the {@code mode} lexer action by calling {@link Lexer//mode} with * the assigned mode */ export class LexerModeAction extends LexerAction { - constructor(mode) { + private readonly mode: number; + + public constructor(mode: number) { super(LexerActionType.MODE); this.mode = mode; } /** *

            This action is implemented by calling {@link Lexer//mode} with the - * value provided by {@link //getMode}.

            + * value provided by {@link getMode}.

            */ - execute(lexer) { + public override execute(lexer: Lexer): void { lexer.mode(this.mode); } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.mode); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerModeAction)) { @@ -39,7 +45,7 @@ export class LexerModeAction extends LexerAction { } } - toString() { + public override toString(): string { return "mode(" + this.mode + ")"; } } diff --git a/src/atn/LexerMoreAction.js b/src/atn/LexerMoreAction.ts similarity index 58% rename from src/atn/LexerMoreAction.js rename to src/atn/LexerMoreAction.ts index 9f130f7..07a6322 100644 --- a/src/atn/LexerMoreAction.js +++ b/src/atn/LexerMoreAction.ts @@ -4,30 +4,34 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +/* eslint-disable jsdoc/require-param */ + +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; /** * Implements the {@code more} lexer action by calling {@link Lexer//more}. * *

            The {@code more} command does not have any parameters, so this action is - * implemented as a singleton instance exposed by {@link //INSTANCE}.

            + * implemented as a singleton instance exposed by {@link INSTANCE}.

            */ export class LexerMoreAction extends LexerAction { - constructor() { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly INSTANCE = new LexerMoreAction(); + + public constructor() { super(LexerActionType.MORE); } /** *

            This action is implemented by calling {@link Lexer//popMode}.

            */ - execute(lexer) { + public override execute(lexer: Lexer): void { lexer.more(); } - toString() { + public override toString(): string { return "more"; } } - -LexerMoreAction.INSTANCE = new LexerMoreAction(); diff --git a/src/atn/LexerPopModeAction.js b/src/atn/LexerPopModeAction.ts similarity index 59% rename from src/atn/LexerPopModeAction.js rename to src/atn/LexerPopModeAction.ts index 14b4003..7d20f4d 100644 --- a/src/atn/LexerPopModeAction.js +++ b/src/atn/LexerPopModeAction.ts @@ -4,30 +4,34 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +/* eslint-disable jsdoc/require-param */ + +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; /** * Implements the {@code popMode} lexer action by calling {@link Lexer//popMode}. * *

            The {@code popMode} command does not have any parameters, so this action is - * implemented as a singleton instance exposed by {@link //INSTANCE}.

            + * implemented as a singleton instance exposed by {@link INSTANCE}.

            */ export class LexerPopModeAction extends LexerAction { - constructor() { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly INSTANCE = new LexerPopModeAction(); + + public constructor() { super(LexerActionType.POP_MODE); } /** *

            This action is implemented by calling {@link Lexer//popMode}.

            */ - execute(lexer) { + public override execute(lexer: Lexer): void { lexer.popMode(); } - toString() { + public override toString(): string { return "popMode"; } } - -LexerPopModeAction.INSTANCE = new LexerPopModeAction(); diff --git a/src/atn/LexerPushModeAction.js b/src/atn/LexerPushModeAction.ts similarity index 65% rename from src/atn/LexerPushModeAction.js rename to src/atn/LexerPushModeAction.ts index 593c1a1..4fe80af 100644 --- a/src/atn/LexerPushModeAction.js +++ b/src/atn/LexerPushModeAction.ts @@ -4,32 +4,38 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +/* eslint-disable jsdoc/require-param */ + +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; /** * Implements the {@code pushMode} lexer action by calling * {@link Lexer//pushMode} with the assigned mode */ export class LexerPushModeAction extends LexerAction { - constructor(mode) { + private readonly mode: number; + + public constructor(mode: number) { super(LexerActionType.PUSH_MODE); this.mode = mode; } /** *

            This action is implemented by calling {@link Lexer//pushMode} with the - * value provided by {@link //getMode}.

            + * value provided by {@link getMode}.

            */ - execute(lexer) { + public override execute(lexer: Lexer): void { lexer.pushMode(this.mode); } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.mode); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerPushModeAction)) { @@ -39,7 +45,7 @@ export class LexerPushModeAction extends LexerAction { } } - toString() { + public override toString(): string { return "pushMode(" + this.mode + ")"; } } diff --git a/src/atn/LexerSkipAction.js b/src/atn/LexerSkipAction.ts similarity index 53% rename from src/atn/LexerSkipAction.js rename to src/atn/LexerSkipAction.ts index 37df5f9..7e3975f 100644 --- a/src/atn/LexerSkipAction.js +++ b/src/atn/LexerSkipAction.ts @@ -4,28 +4,30 @@ * can be found in the LICENSE.txt file in the project root. */ -import { LexerActionType } from "../atn/LexerActionType.js"; +import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; /** * Implements the {@code skip} lexer action by calling {@link Lexer//skip}. * *

            The {@code skip} command does not have any parameters, so this action is - * implemented as a singleton instance exposed by {@link //INSTANCE}.

            + * implemented as a singleton instance exposed by {@link INSTANCE}.

            */ export class LexerSkipAction extends LexerAction { - constructor() { + /** Provides a singleton instance of this parameter-less lexer action. */ + // eslint-disable-next-line @typescript-eslint/naming-convention + public static readonly INSTANCE = new LexerSkipAction(); + + public constructor() { super(LexerActionType.SKIP); } - execute(lexer) { + public execute(lexer: Lexer): void { lexer.skip(); } - toString() { + public override toString(): string { return "skip"; } } - -// Provides a singleton instance of this parameterless lexer action. -LexerSkipAction.INSTANCE = new LexerSkipAction(); diff --git a/src/atn/LexerTypeAction.js b/src/atn/LexerTypeAction.ts similarity index 65% rename from src/atn/LexerTypeAction.js rename to src/atn/LexerTypeAction.ts index 041e446..705de67 100644 --- a/src/atn/LexerTypeAction.js +++ b/src/atn/LexerTypeAction.ts @@ -6,6 +6,8 @@ import { LexerActionType } from "./LexerActionType.js"; import { LexerAction } from "./LexerAction.js"; +import { Lexer } from "../Lexer.js"; +import { HashCode } from "../misc/HashCode.js"; /** * Implements the {@code type} lexer action by calling {@link Lexer//setType} @@ -13,20 +15,23 @@ import { LexerAction } from "./LexerAction.js"; */ export class LexerTypeAction extends LexerAction { - constructor(type) { + public readonly type: number; + + public constructor(type: number) { super(LexerActionType.TYPE); this.type = type; } - execute(lexer) { - lexer.type = this.type; + public override execute(lexer: Lexer): void { + // eslint-disable-next-line no-underscore-dangle + lexer._type = this.type; } - updateHashCode(hash) { + public override updateHashCode(hash: HashCode): void { hash.update(this.actionType, this.type); } - equals(other) { + public override equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof LexerTypeAction)) { @@ -36,7 +41,7 @@ export class LexerTypeAction extends LexerAction { } } - toString() { + public override toString(): string { return "type(" + this.type + ")"; } } diff --git a/src/atn/LoopEndState.d.ts b/src/atn/LoopEndState.d.ts deleted file mode 100644 index 2b545e1..0000000 --- a/src/atn/LoopEndState.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; - -/** - * Mark the end of a * or + loop - */ -export class LoopEndState extends ATNState { -} diff --git a/src/atn/LoopEndState.js b/src/atn/LoopEndState.ts similarity index 81% rename from src/atn/LoopEndState.js rename to src/atn/LoopEndState.ts index b4c490c..866de40 100644 --- a/src/atn/LoopEndState.js +++ b/src/atn/LoopEndState.ts @@ -11,7 +11,9 @@ import { ATNStateType } from "./ATNStateType.js"; * Mark the end of a * or + loop */ export class LoopEndState extends ATNState { - get stateType() { + public loopBackState: ATNState | null = null; + + public override get stateType(): number { return ATNStateType.LOOP_END; } diff --git a/src/atn/NotSetTransition.d.ts b/src/atn/NotSetTransition.d.ts deleted file mode 100644 index 1813581..0000000 --- a/src/atn/NotSetTransition.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { IntervalSet } from "../misc/IntervalSet.js"; -import { SetTransition } from "./SetTransition.js"; - -export declare class NotSetTransition extends SetTransition { - public constructor(target: ATNState, set: IntervalSet); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/NotSetTransition.js b/src/atn/NotSetTransition.ts similarity index 55% rename from src/atn/NotSetTransition.js rename to src/atn/NotSetTransition.ts index b7f1b74..3e84a36 100644 --- a/src/atn/NotSetTransition.js +++ b/src/atn/NotSetTransition.ts @@ -4,21 +4,26 @@ * can be found in the LICENSE.txt file in the project root. */ +import { IntervalSet } from "../misc/IntervalSet.js"; +import { ATNState } from "./ATNState.js"; import { SetTransition } from "./SetTransition.js"; import { TransitionType } from "./TransitionType.js"; export class NotSetTransition extends SetTransition { - constructor(target, set) { + public constructor(target: ATNState, set: IntervalSet) { super(target, set); - this.serializationType = TransitionType.NOT_SET; } - matches(symbol, minVocabSymbol, maxVocabSymbol) { + public override get serializationType(): number { + return TransitionType.NOT_SET; + } + + public override matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean { return symbol >= minVocabSymbol && symbol <= maxVocabSymbol && !super.matches(symbol, minVocabSymbol, maxVocabSymbol); } - toString() { - return '~' + super.toString(); + public override toString(): string { + return "~" + super.toString(); } } diff --git a/src/atn/OrderedATNConfigSet.js b/src/atn/OrderedATNConfigSet.ts similarity index 93% rename from src/atn/OrderedATNConfigSet.js rename to src/atn/OrderedATNConfigSet.ts index 5ecc8df..a33e765 100644 --- a/src/atn/OrderedATNConfigSet.js +++ b/src/atn/OrderedATNConfigSet.ts @@ -8,7 +8,7 @@ import { ATNConfigSet } from "./ATNConfigSet.js"; import { HashSet } from "../misc/HashSet.js"; export class OrderedATNConfigSet extends ATNConfigSet { - constructor() { + public constructor() { super(); this.configLookup = new HashSet(); } diff --git a/src/atn/ParserATNSimulator.d.ts b/src/atn/ParserATNSimulator.d.ts deleted file mode 100644 index f26892e..0000000 --- a/src/atn/ParserATNSimulator.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../Parser.js"; -import { TokenStream } from "../TokenStream.js"; -import { ParserRuleContext } from "../ParserRuleContext.js"; -import { DFA } from "../dfa/DFA.js"; -import { ATN } from "./ATN.js"; -import { ATNSimulator } from "./ATNSimulator.js"; -import { PredictionContextCache } from "./PredictionContextCache.js"; -import { PredictionMode } from "./PredictionMode.js"; - -export declare class ParserATNSimulator extends ATNSimulator { - public predictionMode: PredictionMode; - public decisionToDFA: DFA[]; - public atn: ATN; - public debug?: boolean; - - // eslint-disable-next-line @typescript-eslint/naming-convention - public trace_atn_sim?: boolean; - - public constructor(recog: Parser, atn: ATN, decisionToDFA: DFA[], sharedContextCache: PredictionContextCache); - public adaptivePredict(input: TokenStream, decision: number, outerContext: ParserRuleContext): number; -} diff --git a/src/atn/ParserATNSimulator.js b/src/atn/ParserATNSimulator.ts similarity index 77% rename from src/atn/ParserATNSimulator.js rename to src/atn/ParserATNSimulator.ts index 0749f4c..3a6b134 100644 --- a/src/atn/ParserATNSimulator.js +++ b/src/atn/ParserATNSimulator.ts @@ -4,34 +4,51 @@ * can be found in the LICENSE.txt file in the project root. */ -import { NoViableAltException } from '../NoViableAltException.js'; -import { Token } from '../Token.js'; -import { DFAState } from '../dfa/DFAState.js'; -import { PredPrediction } from '../dfa/PredPrediction.js'; +/* eslint-disable @typescript-eslint/naming-convention, no-underscore-dangle, jsdoc/require-returns */ +/* eslint-disable jsdoc/require-param */ + +import { NoViableAltException } from "../NoViableAltException.js"; +import { Token } from "../Token.js"; +import { DFAState } from "../dfa/DFAState.js"; import { BitSet } from "../misc/BitSet.js"; import { HashSet } from "../misc/HashSet.js"; -import { Interval } from '../misc/Interval.js'; +import { Interval } from "../misc/Interval.js"; import { DoubleDict } from "../utils/DoubleDict.js"; -import { arrayToString } from "../utils/arrayToString.js"; -import { ATN } from './ATN.js'; -import { ATNConfig } from './ATNConfig.js'; -import { ATNConfigSet } from './ATNConfigSet.js'; -import { ATNSimulator } from './ATNSimulator.js'; +import { ATN } from "./ATN.js"; +import { ATNConfig } from "./ATNConfig.js"; +import { ATNConfigSet } from "./ATNConfigSet.js"; +import { ATNSimulator } from "./ATNSimulator.js"; import { ATNStateType } from "./ATNStateType.js"; -import { ActionTransition } from './ActionTransition.js'; +import { ActionTransition } from "./ActionTransition.js"; import { AtomTransition } from "./AtomTransition.js"; -import { NotSetTransition } from './NotSetTransition.js'; -import { PredictionContext } from './PredictionContext.js'; -import { predictionContextFromRuleContext } from './PredictionContextUtils.js'; -import { PredictionMode } from './PredictionMode.js'; -import { RuleContext } from './RuleContext.js'; -import { RuleStopState } from './RuleStopState.js'; -import { RuleTransition } from './RuleTransition.js'; -import { SemanticContext } from './SemanticContext.js'; -import { SetTransition } from './SetTransition.js'; -import { SingletonPredictionContext } from './SingletonPredictionContext.js'; -import { TransitionType } from './TransitionType.js'; -import { Vocabulary } from '../Vocabulary.js'; +import { NotSetTransition } from "./NotSetTransition.js"; +import { PredictionContext } from "./PredictionContext.js"; +import { predictionContextFromRuleContext } from "./PredictionContextUtils.js"; +import { PredictionMode } from "./PredictionMode.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { RuleTransition } from "./RuleTransition.js"; +import { SemanticContext } from "./SemanticContext.js"; +import { SetTransition } from "./SetTransition.js"; +import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; +import { TransitionType } from "./TransitionType.js"; +import { Vocabulary } from "../Vocabulary.js"; + +import { arrayToString } from "../utils/helpers.js"; +import { Parser } from "../Parser.js"; +import { DFA } from "../dfa/DFA.js"; +import { PredictionContextCache } from "./PredictionContextCache.js"; +import { TokenStream } from "../TokenStream.js"; +import { ParserRuleContext } from "../ParserRuleContext.js"; +import { DecisionState } from "./DecisionState.js"; +import { RuleContext } from "../RuleContext.js"; +import { ATNState } from "./ATNState.js"; +import { Transition } from "./Transition.js"; + +import type { EpsilonTransition } from "./EpsilonTransition.js"; +import type { StarLoopEntryState } from "./StarLoopEntryState.js"; +import type { BlockStartState } from "./BlockStartState.js"; +import type { PrecedencePredicateTransition } from "./PrecedencePredicateTransition.js"; +import type { PredicateTransition } from "./PredicateTransition.js"; /** * The embodiment of the adaptive LL(*), ALL(*), parsing strategy. @@ -178,7 +195,7 @@ import { Vocabulary } from '../Vocabulary.js'; *

            * All instances of the same parser share the same decision DFAs through a * static field. Each instance gets its own ATN simulator but they share the - * same {@link //decisionToDFA} field. They also share a + * same {@link decisionToDFA} field. They also share a * {@link PredictionContextCache} object that makes sure that all * {@link PredictionContext} objects are shared among the DFA states. This makes * a big size difference.

            @@ -187,15 +204,15 @@ import { Vocabulary } from '../Vocabulary.js'; * THREAD SAFETY

            * *

            - * The {@link ParserATNSimulator} locks on the {@link //decisionToDFA} field when - * it adds a new DFA object to that array. {@link //addDFAEdge} + * The {@link ParserATNSimulator} locks on the {@link decisionToDFA} field when + * it adds a new DFA object to that array. {@link addDFAEdge} * locks on the DFA for the current decision when setting the - * {@link DFAState//edges} field. {@link //addDFAState} locks on + * {@link DFAState//edges} field. {@link addDFAState} locks on * the DFA for the current decision when looking up a DFA state to see if it * already exists. We must make sure that all requests to add DFA states that * are equivalent result in the same shared DFA object. This is because lots of * threads will be trying to update the DFA at once. The - * {@link //addDFAState} method also locks inside the DFA lock + * {@link addDFAState} method also locks inside the DFA lock * but this time on the shared context cache when it rebuilds the * configurations' {@link PredictionContext} objects using cached * subgraphs/nodes. No other locking occurs, even during DFA simulation. This is @@ -206,7 +223,7 @@ import { Vocabulary } from '../Vocabulary.js'; * targets. The DFA simulator will either find {@link DFAState//edges} to be * {@code null}, to be non-{@code null} and {@code dfa.edges[t]} null, or * {@code dfa.edges[t]} to be non-null. The - * {@link //addDFAEdge} method could be racing to set the field + * {@link addDFAEdge} method could be racing to set the field * but in either case the DFA simulator works; if {@code null}, and requests ATN * simulation. It could also race trying to get {@code dfa.edges[t]}, but either * way it will work because it's not doing a test and set operation.

            @@ -222,7 +239,8 @@ import { Vocabulary } from '../Vocabulary.js'; * mode with the {@link BailErrorStrategy}:

            * *
            - * parser.{@link Parser//getInterpreter() getInterpreter()}.{@link //setPredictionMode setPredictionMode}{@code (}{@link PredictionMode//SLL}{@code )};
            + * parser.{@link Parser//getInterpreter() getInterpreter()}.{@link setPredictionMode setPredictionMode}{@code (}
            + * {@link PredictionMode//SLL}{@code )};
              * parser.{@link Parser//setErrorHandler setErrorHandler}(new {@link BailErrorStrategy}());
              * 
            * @@ -261,44 +279,68 @@ import { Vocabulary } from '../Vocabulary.js'; * the input.

            */ export class ParserATNSimulator extends ATNSimulator { - constructor(parser, atn, decisionToDFA, sharedContextCache) { + public static debug?: boolean; + + // eslint-disable-next-line @typescript-eslint/naming-convention + public static trace_atn_sim = false; + public static debug_add = false; + public static debug_closure = false; + + public static dfa_debug = false; + public static retry_debug = false; + + /** SLL, LL, or LL + exact ambig detection? */ + public predictionMode: number; + public readonly decisionToDFA: DFA[]; + + protected readonly parser: Parser; + + /** + * Each prediction operation uses a cache for merge of prediction contexts. + * Don't keep around as it wastes huge amounts of memory. DoubleKeyMap + * isn't synchronized but we're ok since two threads shouldn't reuse same + * parser/atn sim object because it can only handle one input at a time. + * This maps graphs a and b to merged result c. (a,b)->c. We can avoid + * the merge if we ever see a and b again. Note that (b,a)->c should + * also be examined during cache lookup. + */ + protected mergeCache: DoubleDict | null = null; + + // LAME globals to avoid parameters!!!!! I need these down deep in predTransition + protected _input: TokenStream | null = null; + protected _startIndex = 0; + protected _outerContext: ParserRuleContext | null = null; + protected _dfa: DFA | null = null; + + public constructor(recog: Parser, atn: ATN, decisionToDFA: DFA[], sharedContextCache: PredictionContextCache) { super(atn, sharedContextCache); - this.parser = parser; + this.parser = recog; this.decisionToDFA = decisionToDFA; - // SLL, LL, or LL + exact ambig detection?// - this.predictionMode = PredictionMode.LL; - // LAME globals to avoid parameters!!!!! I need these down deep in predTransition - this._input = null; - this._startIndex = 0; - this._outerContext = null; - this._dfa = null; - /** - * Each prediction operation uses a cache for merge of prediction contexts. - * Don't keep around as it wastes huge amounts of memory. DoubleKeyMap - * isn't synchronized but we're ok since two threads shouldn't reuse same - * parser/atnsim object because it can only handle one input at a time. - * This maps graphs a and b to merged result c. (a,b)→c. We can avoid - * the merge if we ever see a and b again. Note that (b,a)→c should - * also be examined during cache lookup. - */ - this.mergeCache = null; - this.debug = false; - this.debug_closure = false; - this.debug_add = false; - this.trace_atn_sim = false; - this.dfa_debug = false; - this.retry_debug = false; } - reset() { } + protected static getUniqueAlt(configs: ATNConfigSet): number { + let alt = ATN.INVALID_ALT_NUMBER; + for (const c of configs.items) { + if (alt === ATN.INVALID_ALT_NUMBER) { + alt = c.alt; // found first alt + } else if (c.alt !== alt) { + return ATN.INVALID_ALT_NUMBER; + } + } + + return alt; + } + + public reset(): void { } - adaptivePredict(input, decision, outerContext) { - if (this.debug || this.trace_atn_sim) { + public adaptivePredict(input: TokenStream, decision: number, outerContext: ParserRuleContext | null): number { + if (ParserATNSimulator.debug || ParserATNSimulator.trace_atn_sim) { console.log("adaptivePredict decision " + decision + " exec LA(1)==" + this.getLookaheadName(input) + - " line " + input.LT(1).line + ":" + - input.LT(1).column); + " line " + input.LT(1)!.line + ":" + + input.LT(1)!.column); } + this._input = input; this._startIndex = input.index; this._outerContext = outerContext; @@ -322,16 +364,16 @@ export class ParserATNSimulator extends ATNSimulator { } if (s0 === null) { if (outerContext === null) { - outerContext = RuleContext.EMPTY; + outerContext = ParserRuleContext.EMPTY; } - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("predictATN decision " + dfa.decision + " exec LA(1)==" + this.getLookaheadName(input) + ", outerContext=" + outerContext.toString(this.parser.ruleNames)); } const fullCtx = false; - let s0_closure = this.computeStartState(dfa.atnStartState, RuleContext.EMPTY, fullCtx); + let s0_closure = this.computeStartState(dfa.atnStartState!, ParserRuleContext.EMPTY, fullCtx); if (dfa.precedenceDfa) { // If this is a precedence DFA, we use applyPrecedenceFilter @@ -340,19 +382,20 @@ export class ParserATNSimulator extends ATNSimulator { // appropriate start state for the precedence level rather // than simply setting DFA.s0. // - dfa.s0.configs = s0_closure; // not used for prediction but useful to know start configs anyway + dfa.s0!.configs = s0_closure; // not used for prediction but useful to know start configs anyway s0_closure = this.applyPrecedenceFilter(s0_closure); - s0 = this.addDFAState(dfa, new DFAState(null, s0_closure)); + s0 = this.addDFAState(dfa, new DFAState(s0_closure)); dfa.setPrecedenceStartState(this.parser.getPrecedence(), s0); } else { - s0 = this.addDFAState(dfa, new DFAState(null, s0_closure)); + s0 = this.addDFAState(dfa, new DFAState(s0_closure)); dfa.s0 = s0; } } - const alt = this.execATN(dfa, s0, input, index, outerContext); - if (this.debug) { + const alt = this.execATN(dfa, s0, input, index, outerContext!); + if (ParserATNSimulator.debug) { console.log("DFA after predictATN: " + dfa.toString(this.parser.vocabulary)); } + return alt; } finally { this._dfa = null; @@ -392,21 +435,23 @@ export class ParserATNSimulator extends ATNSimulator { * single alt + preds * conflict * conflict + preds - * */ - execATN(dfa, s0, input, startIndex, outerContext) { - if (this.debug || this.trace_atn_sim) { + public execATN(dfa: DFA, s0: DFAState, input: TokenStream, startIndex: number, + outerContext: ParserRuleContext): number { + if (ParserATNSimulator.debug || ParserATNSimulator.trace_atn_sim) { console.log("execATN decision " + dfa.decision + ", DFA state " + s0 + ", LA(1)==" + this.getLookaheadName(input) + - " line " + input.LT(1).line + ":" + input.LT(1).column); + " line " + input.LT(1)!.line + ":" + input.LT(1)!.column); } - let alt; + + let alt: number; let previousD = s0; - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("s0 = " + s0); } + let t = input.LA(1); for (; ;) { // while more work let D = this.getExistingTargetState(previousD, t); @@ -434,9 +479,9 @@ export class ParserATNSimulator extends ATNSimulator { } if (D.requiresFullContext && this.predictionMode !== PredictionMode.SLL) { // IF PREDS, MIGHT RESOLVE TO SINGLE ALT => SLL (or syntax error) - let conflictingAlts = null; + let conflictingAlts: BitSet | null = null; if (D.predicates !== null) { - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("DFA state has preds in DFA sim LL failover"); } const conflictIndex = input.index; @@ -445,10 +490,11 @@ export class ParserATNSimulator extends ATNSimulator { } conflictingAlts = this.evalSemanticContext(D.predicates, outerContext, true); if (conflictingAlts.length === 1) { - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("Full LL avoided"); } - return conflictingAlts.nextSetBit(0); + + return conflictingAlts.nextSetBit(0)!; } if (conflictIndex !== startIndex) { // restore the index so reporting the fallback to full @@ -456,30 +502,35 @@ export class ParserATNSimulator extends ATNSimulator { input.seek(conflictIndex); } } - if (this.dfa_debug) { + if (ParserATNSimulator.dfa_debug) { console.log("ctx sensitive state " + outerContext + " in " + D); } const fullCtx = true; - const s0_closure = this.computeStartState(dfa.atnStartState, outerContext, fullCtx); - this.reportAttemptingFullContext(dfa, conflictingAlts, D.configs, startIndex, input.index); + const s0_closure = this.computeStartState(dfa.atnStartState!, outerContext, fullCtx); + this.reportAttemptingFullContext(dfa, conflictingAlts!, D.configs, startIndex, input.index); alt = this.execATNWithFullContext(dfa, D, s0_closure, input, startIndex, outerContext); + return alt; } + if (D.isAcceptState) { if (D.predicates === null) { return D.prediction; } + const stopIndex = input.index; input.seek(startIndex); const alts = this.evalSemanticContext(D.predicates, outerContext, true); if (alts.length === 0) { throw this.noViableAlt(input, outerContext, D.configs, startIndex); } else if (alts.length === 1) { - return alts.nextSetBit(0); + return alts.nextSetBit(0)!; } else { - // report ambiguity after predicate evaluation to make sure the correct set of ambig alts is reported. + // report ambiguity after predicate evaluation to make sure the correct set of ambig alts is + // reported. this.reportAmbiguity(dfa, D, startIndex, stopIndex, false, alts, D.configs); - return alts.nextSetBit(0); + + return alts.nextSetBit(0)!; } } previousD = D; @@ -498,11 +549,11 @@ export class ParserATNSimulator extends ATNSimulator { * * @param previousD The current DFA state * @param t The next input symbol - * @return The existing target DFA state for the given input symbol + * @returns The existing target DFA state for the given input symbol * {@code t}, or {@code null} if the target state for this edge is not * already cached */ - getExistingTargetState(previousD, t) { + public getExistingTargetState(previousD: DFAState, t: number): DFAState | null { const edges = previousD.edges; if (edges === null) { return null; @@ -519,22 +570,24 @@ export class ParserATNSimulator extends ATNSimulator { * @param previousD The current DFA state * @param t The next input symbol * - * @return The computed target DFA state for the given input symbol + * @returns The computed target DFA state for the given input symbol * {@code t}. If {@code t} does not lead to a valid DFA state, this method - * returns {@link //ERROR + * returns {@link ERROR */ - computeTargetState(dfa, previousD, t) { + public computeTargetState(dfa: DFA, previousD: DFAState, t: number): DFAState { const reach = this.computeReachSet(previousD.configs, t, false); if (reach === null) { this.addDFAEdge(dfa, previousD, t, ATNSimulator.ERROR); + return ATNSimulator.ERROR; } + // create new target state; we'll add to DFA after it's complete - let D = new DFAState(null, reach); + let D = new DFAState(reach); - const predictedAlt = this.getUniqueAlt(reach); + const predictedAlt = ParserATNSimulator.getUniqueAlt(reach); - if (this.debug) { + if (ParserATNSimulator.debug) { const altSubSets = PredictionMode.getConflictingAltSubsets(reach); console.log("SLL altSubSets=" + arrayToString(altSubSets) + /*", previous=" + previousD.configs + */ @@ -555,27 +608,77 @@ export class ParserATNSimulator extends ATNSimulator { D.requiresFullContext = true; // in SLL-only mode, we will stop at this state and return the minimum alt D.isAcceptState = true; - D.prediction = D.configs.conflictingAlts.nextSetBit(0); + D.prediction = D.configs.conflictingAlts.nextSetBit(0)!; } if (D.isAcceptState && D.configs.hasSemanticContext) { - this.predicateDFAState(D, this.atn.getDecisionState(dfa.decision)); + this.predicateDFAState(D, this.atn.getDecisionState(dfa.decision)!); if (D.predicates !== null) { D.prediction = ATN.INVALID_ALT_NUMBER; } } // all adds to dfa are done after we've created full D state - D = this.addDFAEdge(dfa, previousD, t, D); + D = this.addDFAEdge(dfa, previousD, t, D)!; + return D; } - predicateDFAState(dfaState, decisionState) { + public getRuleName(index: number): string { + if (this.parser !== null && index >= 0) { + return this.parser.ruleNames[index]; + } else { + return ""; + } + } + + public getTokenName(t: number): string { + if (t === Token.EOF) { + return "EOF"; + } + + const vocabulary = this.parser != null ? this.parser.vocabulary : Vocabulary.EMPTY_VOCABULARY; + const displayName = vocabulary.getDisplayName(t)!; + if (displayName === t.toString()) { + return displayName; + } + + return displayName + "<" + t + ">"; + } + + public getLookaheadName(input: TokenStream): string { + return this.getTokenName(input.LA(1)); + } + + /** + * Used for debugging in adaptivePredict around execATN but I cut + * it out for clarity now that alg. works well. We can leave this + * "dead" code for a bit + */ + public dumpDeadEndConfigs(e: NoViableAltException): void { + console.log("dead end configs: "); + const decs = e.deadEndConfigs!.items; + for (const c of decs) { + let trans = "no edges"; + if (c.state.transitions.length > 0) { + const t = c.state.transitions[0]; + if (t instanceof AtomTransition) { + trans = "Atom " + this.getTokenName(t.labelValue); + } else if (t instanceof SetTransition) { + const neg = (t instanceof NotSetTransition); + trans = (neg ? "~" : "") + "Set " + t.label; + } + } + console.error(c.toString(this.parser, true) + ":" + trans); + } + } + + protected predicateDFAState(dfaState: DFAState, decisionState: DecisionState): void { // We need to test all predicates, even in DFA states that // uniquely predict alternative. - const nalts = decisionState.transitions.length; + const altCount = decisionState.transitions.length; // Update DFA so reach becomes accept state with (predicate,alt) // pairs if preds found for conflicting alts - const altsToCollectPredsFrom = this.getConflictingAltsOrUniqueAlt(dfaState.configs); - const altToPred = this.getPredsForAmbigAlts(altsToCollectPredsFrom, dfaState.configs, nalts); + const altsToCollectPredsFrom = this.getConflictingAltsOrUniqueAlt(dfaState.configs)!; + const altToPred = this.getPredsForAmbigAlts(altsToCollectPredsFrom, dfaState.configs, altCount); if (altToPred !== null) { dfaState.predicates = this.getPredicatePredictions(altsToCollectPredsFrom, altToPred); dfaState.prediction = ATN.INVALID_ALT_NUMBER; // make sure we use preds @@ -583,19 +686,17 @@ export class ParserATNSimulator extends ATNSimulator { // There are preds in configs but they might go away // when OR'd together like {p}? || NONE == NONE. If neither // alt has preds, resolve to min alt - dfaState.prediction = altsToCollectPredsFrom.nextSetBit(0); + dfaState.prediction = altsToCollectPredsFrom.nextSetBit(0)!; } } // comes back with reach.uniqueAlt set to a valid alt - execATNWithFullContext(dfa, D, // how far we got before failing over - s0, - input, - startIndex, - outerContext) { - if (this.debug || this.trace_atn_sim) { + protected execATNWithFullContext(dfa: DFA, D: DFAState, // how far we got before failing over + s0: ATNConfigSet, input: TokenStream, startIndex: number, outerContext: ParserRuleContext): number { + if (ParserATNSimulator.debug || ParserATNSimulator.trace_atn_sim) { console.log("execATNWithFullContext " + s0); } + const fullCtx = true; let foundExactAmbig = false; let reach; @@ -625,12 +726,12 @@ export class ParserATNSimulator extends ATNSimulator { } } const altSubSets = PredictionMode.getConflictingAltSubsets(reach); - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("LL altSubSets=" + altSubSets + ", predict=" + PredictionMode.getUniqueAlt(altSubSets) + ", resolvesToJustOneViableAlt=" + PredictionMode.resolvesToJustOneViableAlt(altSubSets)); } - reach.uniqueAlt = this.getUniqueAlt(reach); + reach.uniqueAlt = ParserATNSimulator.getUniqueAlt(reach); // unique prediction? if (reach.uniqueAlt !== ATN.INVALID_ALT_NUMBER) { predictedAlt = reach.uniqueAlt; @@ -663,6 +764,7 @@ export class ParserATNSimulator extends ATNSimulator { // not SLL. if (reach.uniqueAlt !== ATN.INVALID_ALT_NUMBER) { this.reportContextSensitivity(dfa, predictedAlt, reach, startIndex, input.index); + return predictedAlt; } // We do not check predicates here because we have checked them @@ -697,8 +799,8 @@ export class ParserATNSimulator extends ATNSimulator { return predictedAlt; } - computeReachSet(closure, t, fullCtx) { - if (this.debug) { + protected computeReachSet(closure: ATNConfigSet, t: number, fullCtx: boolean): ATNConfigSet | null { + if (ParserATNSimulator.debug) { console.log("in computeReachSet, starting closure: " + closure); } if (this.mergeCache === null) { @@ -719,9 +821,8 @@ export class ParserATNSimulator extends ATNSimulator { let skippedStopStates = null; // First figure out where we can reach on input t - for (let i = 0; i < closure.items.length; i++) { - const c = closure.items[i]; - if (this.debug) { + for (const c of closure.items) { + if (ParserATNSimulator.debug) { console.log("testing " + this.getTokenName(t) + " at " + c); } if (c.state instanceof RuleStopState) { @@ -730,26 +831,26 @@ export class ParserATNSimulator extends ATNSimulator { skippedStopStates = []; } skippedStopStates.push(c); - if (this.debug_add) { + if (ParserATNSimulator.debug_add) { console.log("added " + c + " to skippedStopStates"); } } continue; } - for (let j = 0; j < c.state.transitions.length; j++) { - const trans = c.state.transitions[j]; + + for (const trans of c.state.transitions) { const target = this.getReachableTarget(trans, t); if (target !== null) { const cfg = new ATNConfig({ state: target }, c); intermediate.add(cfg, this.mergeCache); - if (this.debug_add) { + if (ParserATNSimulator.debug_add) { console.log("added " + cfg + " to intermediate"); } } } } // Now figure out where the reach operation can take us... - let reach = null; + let reach: ATNConfigSet | null = null; // This block optimizes the reach operation for intermediate sets which // trivially indicate a termination state for the overall @@ -767,7 +868,7 @@ export class ParserATNSimulator extends ATNSimulator { // Also don't pursue the closure if there is unique alternative // among the configurations. reach = intermediate; - } else if (this.getUniqueAlt(intermediate) !== ATN.INVALID_ALT_NUMBER) { + } else if (ParserATNSimulator.getUniqueAlt(intermediate) !== ATN.INVALID_ALT_NUMBER) { // Also don't pursue the closure if there is unique alternative // among the configurations. reach = intermediate; @@ -778,12 +879,13 @@ export class ParserATNSimulator extends ATNSimulator { // if (reach === null) { reach = new ATNConfigSet(fullCtx); - const closureBusy = new HashSet(); + const closureBusy = new HashSet(); const treatEofAsEpsilon = t === Token.EOF; - for (let k = 0; k < intermediate.items.length; k++) { - this.closure(intermediate.items[k], reach, closureBusy, false, fullCtx, treatEofAsEpsilon); + for (const config of intermediate.items) { + this.closure(config, reach, closureBusy, false, fullCtx, treatEofAsEpsilon); } } + if (t === Token.EOF) { // After consuming EOF no additional input is possible, so we are // only interested in configurations which reached the end of the @@ -813,12 +915,12 @@ export class ParserATNSimulator extends ATNSimulator { // multiple alternatives are viable. // if (skippedStopStates !== null && ((!fullCtx) || (!PredictionMode.hasConfigInRuleStopState(reach)))) { - for (let l = 0; l < skippedStopStates.length; l++) { - reach.add(skippedStopStates[l], this.mergeCache); + for (const config of skippedStopStates) { + reach.add(config, this.mergeCache); } } - if (this.trace_atn_sim) { + if (ParserATNSimulator.trace_atn_sim) { console.log("computeReachSet " + closure + " -> " + reach); } @@ -831,35 +933,36 @@ export class ParserATNSimulator extends ATNSimulator { /** * Return a configuration set containing only the configurations from - * {@code configs} which are in a {@link RuleStopState}. If all - * configurations in {@code configs} are already in a rule stop state, this - * method simply returns {@code configs}. + * `configs` which are in a {@link RuleStopState}. If all + * configurations in `configs` are already in a rule stop state, this + * method simply returns `configs`. * *

            When {@code lookToEndOfRule} is true, this method uses - * {@link ATN//nextTokens} for each configuration in {@code configs} which is + * {@link ATN//nextTokens} for each configuration in `configs` which is * not already in a rule stop state to see if a rule stop state is reachable * from the configuration via epsilon-only transitions.

            * * @param configs the configuration set to update * @param lookToEndOfRule when true, this method checks for rule stop states * reachable by epsilon-only transitions from each configuration in - * {@code configs}. + * `configs`. * - * @return {@code configs} if all configurations in {@code configs} are in a + * @returns `configs` if all configurations in `configs` are in a * rule stop state, otherwise return a new configuration set containing only - * the configurations from {@code configs} which are in a rule stop state + * the configurations from `configs` which are in a rule stop state */ - removeAllConfigsNotInRuleStopState(configs, lookToEndOfRule) { + protected removeAllConfigsNotInRuleStopState(configs: ATNConfigSet, lookToEndOfRule: boolean): ATNConfigSet { if (PredictionMode.allConfigsInRuleStopStates(configs)) { return configs; } + const result = new ATNConfigSet(configs.fullCtx); - for (let i = 0; i < configs.items.length; i++) { - const config = configs.items[i]; + for (const config of configs.items) { if (config.state instanceof RuleStopState) { result.add(config, this.mergeCache); continue; } + if (lookToEndOfRule && config.state.epsilonOnlyTransitions) { const nextTokens = this.atn.nextTokens(config.state); if (nextTokens.contains(Token.EPSILON)) { @@ -868,30 +971,33 @@ export class ParserATNSimulator extends ATNSimulator { } } } + return result; } - computeStartState(p, ctx, fullCtx) { + protected computeStartState(p: ATNState, ctx: RuleContext, fullCtx: boolean): ATNConfigSet { // always at least the implicit call to start rule const initialContext = predictionContextFromRuleContext(this.atn, ctx); const configs = new ATNConfigSet(fullCtx); - if (this.trace_atn_sim) { - console.log("computeStartState from ATN state " + p + " initialContext=" + initialContext.toString(this.parser)); + if (ParserATNSimulator.trace_atn_sim) { + console.log("computeStartState from ATN state " + p + " initialContext=" + + initialContext.toString(this.parser)); } for (let i = 0; i < p.transitions.length; i++) { const target = p.transitions[i].target; const c = new ATNConfig({ state: target, alt: i + 1, context: initialContext }, null); - const closureBusy = new HashSet(); + const closureBusy = new HashSet(); this.closure(c, configs, closureBusy, true, fullCtx, false); } + return configs; } /** * This method transforms the start state computed by - * {@link //computeStartState} to the special start state used by a + * {@link computeStartState} to the special start state used by a * precedence DFA for a particular precedence value. The transformation * process applies the following changes to the start state's configuration * set. @@ -940,17 +1046,15 @@ export class ParserATNSimulator extends ATNSimulator { *

            * * @param configs The configuration set computed by - * {@link //computeStartState} as the start state for the DFA. - * @return The transformed configuration set representing the start state + * {@link computeStartState} as the start state for the DFA. + * @returns The transformed configuration set representing the start state * for a precedence DFA at a particular precedence level (determined by * calling {@link Parser//getPrecedence}) */ - applyPrecedenceFilter(configs) { - let config; + protected applyPrecedenceFilter(configs: ATNConfigSet): ATNConfigSet { const statesFromAlt1 = []; const configSet = new ATNConfigSet(configs.fullCtx); - for (let i = 0; i < configs.items.length; i++) { - config = configs.items[i]; + for (const config of configs.items) { // handle alt 1 first if (config.alt !== 1) { continue; @@ -967,8 +1071,8 @@ export class ParserATNSimulator extends ATNSimulator { configSet.add(config, this.mergeCache); } } - for (let i = 0; i < configs.items.length; i++) { - config = configs.items[i]; + + for (const config of configs.items) { if (config.alt === 1) { // already handled continue; @@ -985,10 +1089,11 @@ export class ParserATNSimulator extends ATNSimulator { } configSet.add(config, this.mergeCache); } + return configSet; } - getReachableTarget(trans, ttype) { + protected getReachableTarget(trans: Transition, ttype: number): ATNState | null { if (trans.matches(ttype, 0, this.atn.maxTokenType)) { return trans.target; } else { @@ -996,7 +1101,8 @@ export class ParserATNSimulator extends ATNSimulator { } } - getPredsForAmbigAlts(ambigAlts, configs, nalts) { + protected getPredsForAmbigAlts(ambigAlts: BitSet, configs: ATNConfigSet, + altCount: number): Array | null { // REACH=[1|1|[]|0:0, 1|2|[]|0:1] // altToPred starts as an array of all null contexts. The entry at index i // corresponds to alternative i. altToPred[i] may have one of three values: @@ -1009,48 +1115,53 @@ export class ParserATNSimulator extends ATNSimulator { // // From this, it is clear that NONE||anything==NONE. // - let altToPred = []; - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; + let altToPred: Array | null = []; + for (const c of configs.items) { if (ambigAlts.get(c.alt)) { - altToPred[c.alt] = SemanticContext.orContext(altToPred[c.alt] || null, c.semanticContext); + altToPred[c.alt] = SemanticContext.orContext(altToPred[c.alt] ?? null, c.semanticContext); } } let nPredAlts = 0; - for (let i = 1; i < nalts + 1; i++) { - const pred = altToPred[i] || null; + for (let i = 1; i < altCount + 1; i++) { + const pred = altToPred[i] ?? null; if (pred === null) { altToPred[i] = SemanticContext.NONE; } else if (pred !== SemanticContext.NONE) { nPredAlts += 1; } } - // nonambig alts are null in altToPred + + // non-ambig alts are null in altToPred if (nPredAlts === 0) { altToPred = null; } - if (this.debug) { + + if (ParserATNSimulator.debug) { console.log("getPredsForAmbigAlts result " + arrayToString(altToPred)); } + return altToPred; } - getPredicatePredictions(ambigAlts, altToPred) { + protected getPredicatePredictions(ambigAlts: BitSet, + altToPred: Array): DFAState.PredPrediction[] | null { const pairs = []; let containsPredicate = false; for (let i = 1; i < altToPred.length; i++) { - const pred = altToPred[i]; - // unpredicated is indicated by SemanticContext.NONE + const pred = altToPred[i]!; + // un-predicated is indicated by SemanticContext.NONE if (ambigAlts !== null && ambigAlts.get(i)) { - pairs.push(new PredPrediction(pred, i)); + pairs.push(new DFAState.PredPrediction(pred, i)); } if (pred !== SemanticContext.NONE) { containsPredicate = true; } } + if (!containsPredicate) { return null; } + return pairs; } @@ -1058,7 +1169,7 @@ export class ParserATNSimulator extends ATNSimulator { * This method is used to improve the localization of error messages by * choosing an alternative rather than throwing a * {@link NoViableAltException} in particular prediction scenarios where the - * {@link //ERROR} state was reached during ATN simulation. + * {@link ERROR} state was reached during ATN simulation. * *

            * The default implementation of this method uses the following @@ -1083,7 +1194,7 @@ export class ParserATNSimulator extends ATNSimulator { * the parser. Specifically, this could occur if the only configuration * capable of successfully parsing to the end of the decision rule is * blocked by a semantic predicate. By choosing this alternative within - * {@link //adaptivePredict} instead of throwing a + * {@link adaptivePredict} instead of throwing a * {@link NoViableAltException}, the resulting * {@link FailedPredicateException} in the parser will identify the specific * predicate which is preventing the parser from successfully parsing the @@ -1092,22 +1203,24 @@ export class ParserATNSimulator extends ATNSimulator { *

            * * @param configs The ATN configurations which were valid immediately before - * the {@link //ERROR} state was reached + * the {@link ERROR} state was reached * @param outerContext The is the \gamma_0 initial parser context from the paper * or the parser stack at the instant before prediction commences. * - * @return The value to return from {@link //adaptivePredict}, or + * @returns The value to return from {@link adaptivePredict}, or * {@link ATN//INVALID_ALT_NUMBER} if a suitable alternative was not - * identified and {@link //adaptivePredict} should report an error instead + * identified and {@link adaptivePredict} should report an error instead */ - getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs, outerContext) { - const cfgs = this.splitAccordingToSemanticValidity(configs, outerContext); - const semValidConfigs = cfgs[0]; - const semInvalidConfigs = cfgs[1]; + protected getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs: ATNConfigSet, + outerContext: ParserRuleContext): number { + const splitConfigs = this.splitAccordingToSemanticValidity(configs, outerContext); + const semValidConfigs = splitConfigs[0]; + const semInvalidConfigs = splitConfigs[1]; let alt = this.getAltThatFinishedDecisionEntryRule(semValidConfigs); if (alt !== ATN.INVALID_ALT_NUMBER) { // semantically/syntactically viable path exists return alt; } + // Is there a syntactically valid path with a failed pred? if (semInvalidConfigs.items.length > 0) { alt = this.getAltThatFinishedDecisionEntryRule(semInvalidConfigs); @@ -1115,14 +1228,14 @@ export class ParserATNSimulator extends ATNSimulator { return alt; } } + return ATN.INVALID_ALT_NUMBER; } - getAltThatFinishedDecisionEntryRule(configs) { + protected getAltThatFinishedDecisionEntryRule(configs: ATNConfigSet): number { const alts = []; - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; - if (c.reachesIntoOuterContext > 0 || ((c.state instanceof RuleStopState) && c.context.hasEmptyPath())) { + for (const c of configs.items) { + if (c.reachesIntoOuterContext > 0 || ((c.state instanceof RuleStopState) && c.context!.hasEmptyPath())) { if (alts.indexOf(c.alt) < 0) { alts.push(c.alt); } @@ -1131,7 +1244,7 @@ export class ParserATNSimulator extends ATNSimulator { if (alts.length === 0) { return ATN.INVALID_ALT_NUMBER; } else { - return Math.min.apply(null, alts); + return Math.min(...alts); } } @@ -1143,12 +1256,14 @@ export class ParserATNSimulator extends ATNSimulator { * Create a new set so as not to alter the incoming parameter. * * Assumption: the input stream has been restored to the starting point - * prediction, which is where predicates need to evaluate.*/ - splitAccordingToSemanticValidity(configs, outerContext) { + * prediction, which is where predicates need to evaluate. + */ + protected splitAccordingToSemanticValidity(configs: ATNConfigSet, + outerContext: ParserRuleContext): [ATNConfigSet, ATNConfigSet] { const succeeded = new ATNConfigSet(configs.fullCtx); const failed = new ATNConfigSet(configs.fullCtx); - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; + + for (const c of configs.items) { if (c.semanticContext !== SemanticContext.NONE) { const predicateEvaluationResult = c.semanticContext.evaluate(this.parser, outerContext); if (predicateEvaluationResult) { @@ -1160,6 +1275,7 @@ export class ParserATNSimulator extends ATNSimulator { succeeded.add(c); } } + return [succeeded, failed]; } @@ -1170,10 +1286,11 @@ export class ParserATNSimulator extends ATNSimulator { * then we stop at the first predicate that evaluates to true. This * includes pairs with null predicates. */ - evalSemanticContext(predPredictions, outerContext, complete) { + protected evalSemanticContext(predPredictions: DFAState.PredPrediction[], outerContext: ParserRuleContext, + complete: boolean): BitSet { const predictions = new BitSet(); - for (let i = 0; i < predPredictions.length; i++) { - const pair = predPredictions[i]; + + for (const pair of predPredictions) { if (pair.pred === SemanticContext.NONE) { predictions.set(pair.alt); if (!complete) { @@ -1182,11 +1299,11 @@ export class ParserATNSimulator extends ATNSimulator { continue; } const predicateEvaluationResult = pair.pred.evaluate(this.parser, outerContext); - if (this.debug || this.dfa_debug) { + if (ParserATNSimulator.debug || ParserATNSimulator.dfa_debug) { console.log("eval pred " + pair + "=" + predicateEvaluationResult); } if (predicateEvaluationResult) { - if (this.debug || this.dfa_debug) { + if (ParserATNSimulator.debug || ParserATNSimulator.dfa_debug) { console.log("PREDICT " + pair.alt); } predictions.set(pair.alt); @@ -1195,6 +1312,7 @@ export class ParserATNSimulator extends ATNSimulator { } } } + return predictions; } @@ -1204,28 +1322,31 @@ export class ParserATNSimulator extends ATNSimulator { // waste to pursue the closure. Might have to advance when we do // ambig detection thought :( // - closure(config, configs, closureBusy, collectPredicates, fullCtx, treatEofAsEpsilon) { + protected closure(config: ATNConfig, configs: ATNConfigSet, closureBusy: HashSet, + collectPredicates: boolean, fullCtx: boolean, treatEofAsEpsilon: boolean): void { const initialDepth = 0; this.closureCheckingStopState(config, configs, closureBusy, collectPredicates, fullCtx, initialDepth, treatEofAsEpsilon); } - closureCheckingStopState(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEofAsEpsilon) { - if (this.trace_atn_sim || this.debug_closure) { + protected closureCheckingStopState(config: ATNConfig, configs: ATNConfigSet, closureBusy: HashSet, + collectPredicates: boolean, fullCtx: boolean, depth: number, treatEofAsEpsilon: boolean): void { + if (ParserATNSimulator.trace_atn_sim || ParserATNSimulator.debug_closure) { console.log("closure(" + config.toString(this.parser, true) + ")"); } if (config.state instanceof RuleStopState) { // We hit rule end. If we have context info, use it // run thru all possible stack tops in ctx - if (!config.context.isEmpty()) { + if (config.context && !config.context.isEmpty()) { for (let i = 0; i < config.context.length; i++) { if (config.context.getReturnState(i) === PredictionContext.EMPTY_RETURN_STATE) { if (fullCtx) { - configs.add(new ATNConfig({ state: config.state, context: PredictionContext.EMPTY }, config), this.mergeCache); + configs.add(new ATNConfig({ state: config.state, context: PredictionContext.EMPTY }, + config), this.mergeCache); continue; } else { // we have no context info, just chase follow links (if greedy) - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("FALLING off rule " + this.getRuleName(config.state.ruleIndex)); } this.closure_(config, configs, closureBusy, collectPredicates, @@ -1235,22 +1356,31 @@ export class ParserATNSimulator extends ATNSimulator { } const returnState = this.atn.states[config.context.getReturnState(i)]; const newContext = config.context.getParent(i); // "pop" return state - const parms = { state: returnState, alt: config.alt, context: newContext, semanticContext: config.semanticContext }; - const c = new ATNConfig(parms, null); + const parameters = { + state: returnState, + alt: config.alt, + context: newContext, + semanticContext: config.semanticContext, + }; + const c = new ATNConfig(parameters, null); + // While we have context to pop back from, we may have // gotten that context AFTER having falling off a rule. // Make sure we track that we are now out of context. c.reachesIntoOuterContext = config.reachesIntoOuterContext; - this.closureCheckingStopState(c, configs, closureBusy, collectPredicates, fullCtx, depth - 1, treatEofAsEpsilon); + this.closureCheckingStopState(c, configs, closureBusy, collectPredicates, fullCtx, depth - 1, + treatEofAsEpsilon); } + return; } else if (fullCtx) { // reached end of start rule configs.add(config, this.mergeCache); + return; } else { // else if we have no context info, just chase follow links (if greedy) - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("FALLING off rule " + this.getRuleName(config.state.ruleIndex)); } } @@ -1259,7 +1389,8 @@ export class ParserATNSimulator extends ATNSimulator { } // Do the actual work of walking epsilon edges// - closure_(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEofAsEpsilon) { + protected closure_(config: ATNConfig, configs: ATNConfigSet, closureBusy: HashSet, + collectPredicates: boolean, fullCtx: boolean, depth: number, treatEofAsEpsilon: boolean): void { const p = config.state; // optimization if (!p.epsilonOnlyTransitions) { @@ -1267,9 +1398,11 @@ export class ParserATNSimulator extends ATNSimulator { // make sure to not return here, because EOF transitions can act as // both epsilon transitions and non-epsilon transitions. } + for (let i = 0; i < p.transitions.length; i++) { - if (i === 0 && this.canDropLoopEntryEdgeInLeftRecursiveRule(config)) + if (i === 0 && this.canDropLoopEntryEdgeInLeftRecursiveRule(config)) { continue; + } const t = p.transitions[i]; const continueCollecting = collectPredicates && !(t instanceof ActionTransition); @@ -1283,7 +1416,8 @@ export class ParserATNSimulator extends ATNSimulator { // come in handy and we avoid evaluating context dependent // preds if this is > 0. if (this._dfa !== null && this._dfa.precedenceDfa) { - if (t.outermostPrecedenceReturn === this._dfa.atnStartState.ruleIndex) { + const outermostPrecedenceReturn = (t as EpsilonTransition).outermostPrecedenceReturn; + if (outermostPrecedenceReturn === this._dfa.atnStartState?.ruleIndex) { c.precedenceFilterSuppressed = true; } } @@ -1293,9 +1427,11 @@ export class ParserATNSimulator extends ATNSimulator { // avoid infinite recursion for right-recursive rules continue; } - configs.dipsIntoOuterContext = true; // TODO: can remove? only care when we add to set per middle of this method + + // TODO: can remove? only care when we add to set per middle of this method + configs.dipsIntoOuterContext = true; newDepth -= 1; - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("dips into outer ctx: " + c); } } else { @@ -1310,92 +1446,88 @@ export class ParserATNSimulator extends ATNSimulator { } } } - this.closureCheckingStopState(c, configs, closureBusy, continueCollecting, fullCtx, newDepth, treatEofAsEpsilon); + this.closureCheckingStopState(c, configs, closureBusy, continueCollecting, fullCtx, newDepth, + treatEofAsEpsilon); } } } - canDropLoopEntryEdgeInLeftRecursiveRule(config) { - // return False + protected canDropLoopEntryEdgeInLeftRecursiveRule(config: ATNConfig): boolean { const p = config.state; // First check to see if we are in StarLoopEntryState generated during // left-recursion elimination. For efficiency, also check if // the context has an empty stack case. If so, it would mean // global FOLLOW so we can't perform optimization // Are we the special loop entry/exit state? or SLL wildcard - if (p.stateType !== ATNStateType.STAR_LOOP_ENTRY) + if (p.stateType !== ATNStateType.STAR_LOOP_ENTRY || !config.context) { return false; - if (p.stateType !== ATNStateType.STAR_LOOP_ENTRY || !p.precedenceRuleDecision || - config.context.isEmpty() || config.context.hasEmptyPath()) + } + + if (!(p as StarLoopEntryState).precedenceRuleDecision || + config.context.isEmpty() || config.context.hasEmptyPath()) { return false; + } // Require all return states to return back to the same rule that p is in. const numCtxs = config.context.length; for (let i = 0; i < numCtxs; i++) { // for each stack context const returnState = this.atn.states[config.context.getReturnState(i)]; - if (returnState.ruleIndex !== p.ruleIndex) + if (returnState!.ruleIndex !== p.ruleIndex) { return false; + } } - const decisionStartState = p.transitions[0].target; - const blockEndStateNum = decisionStartState.endState.stateNumber; + const decisionStartState = p.transitions[0].target as BlockStartState; + const blockEndStateNum = decisionStartState.endState!.stateNumber; const blockEndState = this.atn.states[blockEndStateNum]; // Verify that the top of each stack context leads to loop entry/exit // state through epsilon edges and w/o leaving rule. for (let i = 0; i < numCtxs; i++) { // for each stack context const returnStateNumber = config.context.getReturnState(i); - const returnState = this.atn.states[returnStateNumber]; + const returnState = this.atn.states[returnStateNumber]!; // all states must have single outgoing epsilon edge - if (returnState.transitions.length !== 1 || !returnState.transitions[0].isEpsilon) - return false; + if (returnState.transitions.length !== 1 || !returnState.transitions[0].isEpsilon) { return false; } // Look for prefix op case like 'not expr', (' type ')' expr const returnStateTarget = returnState.transitions[0].target; - if (returnState.stateType === ATNStateType.BLOCK_END && returnStateTarget === p) - continue; + if (returnState.stateType === ATNStateType.BLOCK_END && returnStateTarget === p) { continue; } // Look for 'expr op expr' or case where expr's return state is block end // of (...)* internal block; the block end points to loop back // which points to p but we don't need to check that - if (returnState === blockEndState) - continue; + if (returnState === blockEndState) { continue; } // Look for ternary expr ? expr : expr. The return state points at block end, // which points at loop entry state - if (returnStateTarget === blockEndState) - continue; + if (returnStateTarget === blockEndState) { continue; } // Look for complex prefix 'between expr and expr' case where 2nd expr's // return state points at block end state of (...)* internal block if (returnStateTarget.stateType === ATNStateType.BLOCK_END && returnStateTarget.transitions.length === 1 - && returnStateTarget.transitions[0].isEpsilon && returnStateTarget.transitions[0].target === p) + && returnStateTarget.transitions[0].isEpsilon && returnStateTarget.transitions[0].target === p) { continue; + } // anything else ain't conforming return false; } - return true; - } - getRuleName(index) { - if (this.parser !== null && index >= 0) { - return this.parser.ruleNames[index]; - } else { - return ""; - } + return true; } - getEpsilonTarget(config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon) { + protected getEpsilonTarget(config: ATNConfig, t: Transition, collectPredicates: boolean, inContext: boolean, + fullCtx: boolean, treatEofAsEpsilon: boolean): ATNConfig | null { switch (t.serializationType) { case TransitionType.RULE: - return this.ruleTransition(config, t); + return this.ruleTransition(config, t as RuleTransition); case TransitionType.PRECEDENCE: - return this.precedenceTransition(config, t, collectPredicates, inContext, fullCtx); + return this.precedenceTransition(config, t as PrecedencePredicateTransition, collectPredicates, + inContext, fullCtx); case TransitionType.PREDICATE: - return this.predTransition(config, t, collectPredicates, inContext, fullCtx); + return this.predTransition(config, t as PredicateTransition, collectPredicates, inContext, fullCtx); case TransitionType.ACTION: - return this.actionTransition(config, t); + return this.actionTransition(config, t as ActionTransition); case TransitionType.EPSILON: return new ATNConfig({ state: t.target }, config); case TransitionType.ATOM: @@ -1408,38 +1540,42 @@ export class ParserATNSimulator extends ATNSimulator { return new ATNConfig({ state: t.target }, config); } } + return null; default: return null; } } - actionTransition(config, t) { - if (this.debug) { + protected actionTransition(config: ATNConfig, t: ActionTransition): ATNConfig { + if (ParserATNSimulator.debug) { const index = t.actionIndex === -1 ? 65535 : t.actionIndex; console.log("ACTION edge " + t.ruleIndex + ":" + index); } + return new ATNConfig({ state: t.target }, config); } - precedenceTransition(config, pt, collectPredicates, inContext, fullCtx) { - if (this.debug) { + protected precedenceTransition(config: ATNConfig, pt: PrecedencePredicateTransition, collectPredicates: boolean, + inContext: boolean, fullCtx: boolean): ATNConfig | null { + if (ParserATNSimulator.debug) { console.log("PRED (collectPredicates=" + collectPredicates + ") " + pt.precedence + ">=_p, ctx dependent=true"); if (this.parser !== null) { console.log("context surrounding pred is " + arrayToString(this.parser.getRuleInvocationStack())); } } + let c = null; if (collectPredicates && inContext) { - if (fullCtx) { + if (fullCtx && this._input) { // In full context mode, we can evaluate predicates on-the-fly // during closure, which dramatically reduces the size of // the config sets. It also obviates the need to test predicates // later during conflict resolution. const currentPosition = this._input.index; this._input.seek(this._startIndex); - const predSucceeds = pt.getPredicate().evaluate(this.parser, this._outerContext); + const predSucceeds = pt.getPredicate().evaluate(this.parser, this._outerContext!); this._input.seek(currentPosition); if (predSucceeds) { c = new ATNConfig({ state: pt.target }, config); // no pred context @@ -1451,14 +1587,16 @@ export class ParserATNSimulator extends ATNSimulator { } else { c = new ATNConfig({ state: pt.target }, config); } - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("config from pred transition=" + c); } + return c; } - predTransition(config, pt, collectPredicates, inContext, fullCtx) { - if (this.debug) { + protected predTransition(config: ATNConfig, pt: PredicateTransition, collectPredicates: boolean, inContext: boolean, + fullCtx: boolean): ATNConfig | null { + if (ParserATNSimulator.debug) { console.log("PRED (collectPredicates=" + collectPredicates + ") " + pt.ruleIndex + ":" + pt.predIndex + ", ctx dependent=" + pt.isCtxDependent); if (this.parser !== null) { @@ -1467,14 +1605,14 @@ export class ParserATNSimulator extends ATNSimulator { } let c = null; if (collectPredicates && ((pt.isCtxDependent && inContext) || !pt.isCtxDependent)) { - if (fullCtx) { + if (fullCtx && this._input) { // In full context mode, we can evaluate predicates on-the-fly // during closure, which dramatically reduces the size of // the config sets. It also obviates the need to test predicates // later during conflict resolution. const currentPosition = this._input.index; this._input.seek(this._startIndex); - const predSucceeds = pt.getPredicate().evaluate(this.parser, this._outerContext); + const predSucceeds = pt.getPredicate().evaluate(this.parser, this._outerContext!); this._input.seek(currentPosition); if (predSucceeds) { c = new ATNConfig({ state: pt.target }, config); // no pred context @@ -1486,24 +1624,27 @@ export class ParserATNSimulator extends ATNSimulator { } else { c = new ATNConfig({ state: pt.target }, config); } - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("config from pred transition=" + c); } + return c; } - ruleTransition(config, t) { - if (this.debug) { + protected ruleTransition(config: ATNConfig, t: RuleTransition): ATNConfig { + if (ParserATNSimulator.debug) { console.log("CALL rule " + this.getRuleName(t.target.ruleIndex) + ", ctx=" + config.context); } const returnState = t.followState; const newContext = SingletonPredictionContext.create(config.context, returnState.stateNumber); + return new ATNConfig({ state: t.target, context: newContext }, config); } - getConflictingAlts(configs) { - const altsets = PredictionMode.getConflictingAltSubsets(configs); - return PredictionMode.getAlts(altsets); + protected getConflictingAlts(configs: ATNConfigSet): BitSet { + const altSets = PredictionMode.getConflictingAltSubsets(configs); + + return PredictionMode.getAlts(altSets); } /** @@ -1523,7 +1664,7 @@ export class ParserATNSimulator extends ATNSimulator { * we don't consider any conflicts that include alternative 2. So, we * ignore the conflict between alts 1 and 2. We ignore a set of * conflicting alts when there is an intersection with an alternative - * associated with a single alt state in the state→config-list map. + * associated with a single alt state in the state -> config-list map. * * It's also the case that we might have two conflicting configurations but * also a 3rd nonconflicting configuration for a different alternative: @@ -1542,114 +1683,66 @@ export class ParserATNSimulator extends ATNSimulator { * ignore a set of conflicting alts when we have an alternative * that we still need to pursue */ - getConflictingAltsOrUniqueAlt(configs) { - let conflictingAlts = null; + protected getConflictingAltsOrUniqueAlt(configs: ATNConfigSet): BitSet | null { + let conflictingAlts; if (configs.uniqueAlt !== ATN.INVALID_ALT_NUMBER) { conflictingAlts = new BitSet(); conflictingAlts.set(configs.uniqueAlt); } else { conflictingAlts = configs.conflictingAlts; } - return conflictingAlts; - } - getTokenName(t) { - if (t === Token.EOF) { - return "EOF"; - } - - const vocabulary = this.parser != null ? this.parser.vocabulary : Vocabulary.EMPTY_VOCABULARY; - const displayName = vocabulary.getDisplayName(t); - if (displayName.equals(t.toString())) { - return displayName; - } - - return displayName + "<" + t + ">"; - } - - getLookaheadName(input) { - return this.getTokenName(input.LA(1)); - } - - /** - * Used for debugging in adaptivePredict around execATN but I cut - * it out for clarity now that alg. works well. We can leave this - * "dead" code for a bit - */ - dumpDeadEndConfigs(nvae) { - console.log("dead end configs: "); - const decs = nvae.getDeadEndConfigs(); - for (let i = 0; i < decs.length; i++) { - const c = decs[i]; - let trans = "no edges"; - if (c.state.transitions.length > 0) { - const t = c.state.transitions[0]; - if (t instanceof AtomTransition) { - trans = "Atom " + this.getTokenName(t.label); - } else if (t instanceof SetTransition) { - const neg = (t instanceof NotSetTransition); - trans = (neg ? "~" : "") + "Set " + t.set; - } - } - console.error(c.toString(this.parser, true) + ":" + trans); - } + return conflictingAlts; } - noViableAlt(input, outerContext, configs, startIndex) { + protected noViableAlt(input: TokenStream, outerContext: ParserRuleContext, configs: ATNConfigSet, + startIndex: number): NoViableAltException { return new NoViableAltException(this.parser, input, input.get(startIndex), input.LT(1), configs, outerContext); } - getUniqueAlt(configs) { - let alt = ATN.INVALID_ALT_NUMBER; - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; - if (alt === ATN.INVALID_ALT_NUMBER) { - alt = c.alt; // found first alt - } else if (c.alt !== alt) { - return ATN.INVALID_ALT_NUMBER; - } - } - return alt; - } - /** * Add an edge to the DFA, if possible. This method calls - * {@link //addDFAState} to ensure the {@code to} state is present in the + * {@link addDFAState} to ensure the {@code to} state is present in the * DFA. If {@code from} is {@code null}, or if {@code t} is outside the * range of edges that can be represented in the DFA tables, this method * returns without adding the edge to the DFA. * *

            If {@code to} is {@code null}, this method returns {@code null}. * Otherwise, this method returns the {@link DFAState} returned by calling - * {@link //addDFAState} for the {@code to} state.

            + * {@link addDFAState} for the {@code to} state.

            * * @param dfa The DFA * @param from_ The source state for the edge * @param t The input symbol * @param to The target state for the edge * - * @return If {@code to} is {@code null}, this method returns {@code null}; - * otherwise this method returns the result of calling {@link //addDFAState} + * @returns If {@code to} is {@code null}, this method returns {@code null}; + * otherwise this method returns the result of calling {@link addDFAState} * on {@code to} */ - addDFAEdge(dfa, from_, t, to) { - if (this.debug) { + protected addDFAEdge(dfa: DFA, from_: DFAState, t: number, to: DFAState): DFAState | null { + if (ParserATNSimulator.debug) { console.log("EDGE " + from_ + " -> " + to + " upon " + this.getTokenName(t)); } if (to === null) { return null; } + to = this.addDFAState(dfa, to); // used existing if possible not incoming if (from_ === null || t < -1 || t > this.atn.maxTokenType) { return to; } + if (from_.edges === null) { - from_.edges = []; + from_.edges = new Array(this.atn.maxTokenType + 2); + from_.edges.fill(null); } + from_.edges[t + 1] = to; // connect - if (this.debug) { - console.log("DFA=\n" + dfa.toString(this.parser != null ? parser.vocabulary : Vocabulary.EMPTY_VOCABULARY)); + if (ParserATNSimulator.debug) { + console.log("DFA=\n" + + dfa.toString(this.parser != null ? this.parser.vocabulary : Vocabulary.EMPTY_VOCABULARY)); } return to; @@ -1661,22 +1754,23 @@ export class ParserATNSimulator extends ATNSimulator { * is already in the DFA, the existing state is returned. Otherwise this * method returns {@code D} after adding it to the DFA. * - *

            If {@code D} is {@link //ERROR}, this method returns {@link //ERROR} and + *

            If {@code D} is {@link ERROR}, this method returns {@link ERROR} and * does not change the DFA.

            * * @param dfa The dfa * @param D The DFA state to add - * @return The state stored in the DFA. This will be either the existing + * @returns The state stored in the DFA. This will be either the existing * state if {@code D} is already in the DFA, or {@code D} itself if the * state was not already present */ - addDFAState(dfa, D) { + protected addDFAState(dfa: DFA, D: DFAState): DFAState { if (D === ATNSimulator.ERROR) { return D; } const existing = dfa.states.get(D); if (existing !== null) { - if (this.trace_atn_sim) console.log("addDFAState " + D + " exists"); + if (ParserATNSimulator.trace_atn_sim) { console.log("addDFAState " + D + " exists"); } + return existing; } D.stateNumber = dfa.states.length; @@ -1685,47 +1779,55 @@ export class ParserATNSimulator extends ATNSimulator { D.configs.setReadonly(true); } - if (this.trace_atn_sim) console.log("addDFAState new " + D); + if (ParserATNSimulator.trace_atn_sim) { console.log("addDFAState new " + D); } dfa.states.add(D); - if (this.debug) { + if (ParserATNSimulator.debug) { console.log("adding new DFA state: " + D); } + return D; } - reportAttemptingFullContext(dfa, conflictingAlts, configs, startIndex, stopIndex) { - if (this.debug || this.retry_debug) { + protected reportAttemptingFullContext(dfa: DFA, conflictingAlts: BitSet, configs: ATNConfigSet, startIndex: number, + stopIndex: number): void { + if (ParserATNSimulator.debug || ParserATNSimulator.retry_debug) { const interval = new Interval(startIndex, stopIndex + 1); console.log("reportAttemptingFullContext decision=" + dfa.decision + ":" + configs + ", input=" + this.parser.tokenStream.getText(interval)); } + if (this.parser !== null) { - this.parser.getErrorListenerDispatch().reportAttemptingFullContext(this.parser, dfa, startIndex, stopIndex, conflictingAlts, configs); + this.parser.getErrorListenerDispatch().reportAttemptingFullContext(this.parser, dfa, startIndex, stopIndex, + conflictingAlts, configs); } } - reportContextSensitivity(dfa, prediction, configs, startIndex, stopIndex) { - if (this.debug || this.retry_debug) { + protected reportContextSensitivity(dfa: DFA, prediction: number, configs: ATNConfigSet, startIndex: number, + stopIndex: number): void { + if (ParserATNSimulator.debug || ParserATNSimulator.retry_debug) { const interval = new Interval(startIndex, stopIndex + 1); console.log("reportContextSensitivity decision=" + dfa.decision + ":" + configs + ", input=" + this.parser.tokenStream.getText(interval)); } + if (this.parser !== null) { - this.parser.getErrorListenerDispatch().reportContextSensitivity(this.parser, dfa, startIndex, stopIndex, prediction, configs); + this.parser.getErrorListenerDispatch().reportContextSensitivity(this.parser, dfa, startIndex, stopIndex, + prediction, configs); } } // If context sensitive parsing, we know it's ambiguity not conflict// - reportAmbiguity(dfa, D, startIndex, stopIndex, - exact, ambigAlts, configs) { - if (this.debug || this.retry_debug) { + protected reportAmbiguity(dfa: DFA, D: DFAState, startIndex: number, stopIndex: number, + exact: boolean, ambigAlts: BitSet | null, configs: ATNConfigSet): void { + if (ParserATNSimulator.debug || ParserATNSimulator.retry_debug) { const interval = new Interval(startIndex, stopIndex + 1); console.log("reportAmbiguity " + ambigAlts + ":" + configs + ", input=" + this.parser.tokenStream.getText(interval)); } if (this.parser !== null) { - this.parser.getErrorListenerDispatch().reportAmbiguity(this.parser, dfa, startIndex, stopIndex, exact, ambigAlts, configs); + this.parser.getErrorListenerDispatch().reportAmbiguity(this.parser, dfa, startIndex, stopIndex, exact, + ambigAlts, configs); } } } diff --git a/src/atn/PlusBlockStartState.js b/src/atn/PlusBlockStartState.js deleted file mode 100644 index e06c1d4..0000000 --- a/src/atn/PlusBlockStartState.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNStateType } from "./ATNStateType.js"; -import { BlockStartState } from "./BlockStartState.js"; - -/** - * Start of {@code (A|B|...)+} loop. Technically a decision state, but - * we don't use for code generation; somebody might need it, so I'm defining - * it for completeness. In reality, the {@link PlusLoopbackState} node is the - * real decision-making note for {@code A+} - */ -export class PlusBlockStartState extends BlockStartState { - get stateType() { - return ATNStateType.PLUS_BLOCK_START; - } - -} diff --git a/src/atn/PlusBlockStartState.d.ts b/src/atn/PlusBlockStartState.ts similarity index 82% rename from src/atn/PlusBlockStartState.d.ts rename to src/atn/PlusBlockStartState.ts index f187dfd..9d7025c 100644 --- a/src/atn/PlusBlockStartState.d.ts +++ b/src/atn/PlusBlockStartState.ts @@ -4,6 +4,7 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ATNStateType } from "./ATNStateType.js"; import { BlockStartState } from "./BlockStartState.js"; import { PlusLoopbackState } from "./PlusLoopbackState.js"; @@ -15,4 +16,9 @@ import { PlusLoopbackState } from "./PlusLoopbackState.js"; */ export class PlusBlockStartState extends BlockStartState { public loopBackState: PlusLoopbackState; + + public override get stateType(): number { + return ATNStateType.PLUS_BLOCK_START; + } + } diff --git a/src/atn/PlusLoopbackState.d.ts b/src/atn/PlusLoopbackState.d.ts deleted file mode 100644 index c7cc4a3..0000000 --- a/src/atn/PlusLoopbackState.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DecisionState } from "./DecisionState.js"; - -/** - * Decision state for {@code A+} and {@code (A|B)+}. It has two transitions: - * one to the loop back to start of the block and one to exit. - */ -export class PlusLoopbackState extends DecisionState { -} diff --git a/src/atn/PlusLoopbackState.js b/src/atn/PlusLoopbackState.ts similarity index 92% rename from src/atn/PlusLoopbackState.js rename to src/atn/PlusLoopbackState.ts index 9d4aab4..564c14a 100644 --- a/src/atn/PlusLoopbackState.js +++ b/src/atn/PlusLoopbackState.ts @@ -12,7 +12,7 @@ import { ATNStateType } from "./ATNStateType.js"; * one to the loop back to start of the block and one to exit. */ export class PlusLoopbackState extends DecisionState { - get stateType() { + public override get stateType(): number { return ATNStateType.PLUS_LOOP_BACK; } diff --git a/src/atn/PrecedencePredicate.d.ts b/src/atn/PrecedencePredicate.d.ts deleted file mode 100644 index 25539f5..0000000 --- a/src/atn/PrecedencePredicate.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../Parser.js"; -import { RuleContext } from "./RuleContext.js"; -import { SemanticContext } from "./SemanticContext.js"; - -export declare class PrecedencePredicate extends SemanticContext { - public precedence: number; - - public constructor(precedence?: number); - - public evaluate(parser: Parser, parserCallStack: RuleContext): boolean; - public evalPrecedence(parser: Parser, parserCallStack: RuleContext): SemanticContext; - - public compareTo(o: PrecedencePredicate): number; - public equals(other: unknown): boolean; - public toString(): string; -} diff --git a/src/atn/PrecedencePredicate.js b/src/atn/PrecedencePredicate.js deleted file mode 100644 index 8ea7423..0000000 --- a/src/atn/PrecedencePredicate.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { SemanticContext } from "./SemanticContext.js"; - -export class PrecedencePredicate extends SemanticContext { - - constructor(precedence) { - super(); - this.precedence = precedence === undefined ? 0 : precedence; - } - - evaluate(parser, outerContext) { - return parser.precpred(outerContext, this.precedence); - } - - evalPrecedence(parser, outerContext) { - if (parser.precpred(outerContext, this.precedence)) { - return SemanticContext.NONE; - } else { - return null; - } - } - - compareTo(other) { - return this.precedence - other.precedence; - } - - updateHashCode(hash) { - hash.update(this.precedence); - } - - equals(other) { - if (this === other) { - return true; - } else if (!(other instanceof PrecedencePredicate)) { - return false; - } else { - return this.precedence === other.precedence; - } - } - - toString() { - return "{" + this.precedence + ">=prec}?"; - } - -} - -// HORRIBLE workaround circular import, avoiding dynamic import -SemanticContext.PrecedencePredicate = PrecedencePredicate; diff --git a/src/atn/PrecedencePredicateTransition.d.ts b/src/atn/PrecedencePredicateTransition.d.ts deleted file mode 100644 index 446f0b8..0000000 --- a/src/atn/PrecedencePredicateTransition.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { AbstractPredicateTransition } from "../atn/AbstractPredicateTransition.js"; -import { PrecedencePredicate } from "../atn/PrecedencePredicate.js"; -import { ATNState } from "../atn/ATNState.js"; - -export declare class PrecedencePredicateTransition extends AbstractPredicateTransition { - public precedence: number; - - public constructor(target: ATNState, precedence: number); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public getPredicate(): PrecedencePredicate; - public toString(): string; -} diff --git a/src/atn/PrecedencePredicateTransition.js b/src/atn/PrecedencePredicateTransition.js deleted file mode 100644 index e768140..0000000 --- a/src/atn/PrecedencePredicateTransition.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { PrecedencePredicate } from "../atn/PrecedencePredicate.js"; -import { AbstractPredicateTransition } from "../atn/AbstractPredicateTransition.js"; -import { TransitionType } from "./TransitionType.js"; - -export class PrecedencePredicateTransition extends AbstractPredicateTransition { - constructor(target, precedence) { - super(target); - this.serializationType = TransitionType.PRECEDENCE; - this.precedence = precedence; - this.isEpsilon = true; - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return false; - } - - getPredicate() { - return new PrecedencePredicate(this.precedence); - } - - toString() { - return this.precedence + " >= _p"; - } -} diff --git a/src/atn/PrecedencePredicateTransition.ts b/src/atn/PrecedencePredicateTransition.ts new file mode 100644 index 0000000..335b09b --- /dev/null +++ b/src/atn/PrecedencePredicateTransition.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { SemanticContext } from "./SemanticContext.js"; +import { AbstractPredicateTransition } from "./AbstractPredicateTransition.js"; +import { TransitionType } from "./TransitionType.js"; +import { ATNState } from "./ATNState.js"; + +export class PrecedencePredicateTransition extends AbstractPredicateTransition { + public readonly precedence: number; + + public constructor(target: ATNState, precedence: number) { + super(target); + this.precedence = precedence; + } + + public override get isEpsilon(): boolean { + return true; + } + + public matches(_symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return false; + } + + public getPredicate(): SemanticContext.PrecedencePredicate { + return new SemanticContext.PrecedencePredicate(this.precedence); + } + + public get serializationType(): number { + return TransitionType.PRECEDENCE; + } + + public override toString(): string { + return this.precedence + " >= _p"; + } +} diff --git a/src/atn/Predicate.d.ts b/src/atn/Predicate.d.ts deleted file mode 100644 index 63c107c..0000000 --- a/src/atn/Predicate.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../Parser.js"; -import { RuleContext } from "./RuleContext.js"; -import { SemanticContext } from "./SemanticContext.js"; - -export declare class Predicate extends SemanticContext { - public constructor(ruleIndex: number, predIndex: number, isCtxDependent: boolean); - - public evaluate(parser: Parser, outerContext: RuleContext): boolean; - public equals(obj: unknown): boolean; - public toString(): string; -} diff --git a/src/atn/Predicate.js b/src/atn/Predicate.js deleted file mode 100644 index 8dd946d..0000000 --- a/src/atn/Predicate.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { SemanticContext } from "./SemanticContext.js"; - -export class Predicate extends SemanticContext { - - constructor(ruleIndex, predIndex, isCtxDependent) { - super(); - this.ruleIndex = ruleIndex === undefined ? -1 : ruleIndex; - this.predIndex = predIndex === undefined ? -1 : predIndex; - this.isCtxDependent = isCtxDependent === undefined ? false : isCtxDependent; // e.g., $i ref in pred - } - - evaluate(parser, outerContext) { - const localctx = this.isCtxDependent ? outerContext : null; - return parser.sempred(localctx, this.ruleIndex, this.predIndex); - } - - updateHashCode(hash) { - hash.update(this.ruleIndex, this.predIndex, this.isCtxDependent); - } - - equals(other) { - if (this === other) { - return true; - } else if (!(other instanceof Predicate)) { - return false; - } else { - return this.ruleIndex === other.ruleIndex && - this.predIndex === other.predIndex && - this.isCtxDependent === other.isCtxDependent; - } - } - - toString() { - return "{" + this.ruleIndex + ":" + this.predIndex + "}?"; - } -} - -/** - * The default {@link SemanticContext}, which is semantically equivalent to - * a predicate of the form {@code {true}?} - */ -SemanticContext.NONE = new Predicate(); diff --git a/src/atn/PredicateTransition.d.ts b/src/atn/PredicateTransition.d.ts deleted file mode 100644 index 5b37831..0000000 --- a/src/atn/PredicateTransition.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { AbstractPredicateTransition } from "../atn/AbstractPredicateTransition.js"; -import { Predicate } from "../atn/Predicate.js"; -import { ATNState } from "../atn/ATNState.js"; - -export declare class PredicateTransition extends AbstractPredicateTransition { - public ruleIndex: number; - public predIndex: number; - public isCtxDependent: boolean; - public isEpsilon: boolean; - - public constructor(target: ATNState, ruleIndex: number, predIndex: number, isCtxDependent: boolean); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public getPredicate(): Predicate; - public toString(): string; -} diff --git a/src/atn/PredicateTransition.js b/src/atn/PredicateTransition.js deleted file mode 100644 index 3420846..0000000 --- a/src/atn/PredicateTransition.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Predicate } from "../atn/Predicate.js"; -import { AbstractPredicateTransition } from "../atn/AbstractPredicateTransition.js"; -import { TransitionType } from "./TransitionType.js"; - -export class PredicateTransition extends AbstractPredicateTransition { - constructor(target, ruleIndex, predIndex, isCtxDependent) { - super(target); - this.serializationType = TransitionType.PREDICATE; - this.ruleIndex = ruleIndex; - this.predIndex = predIndex; - this.isCtxDependent = isCtxDependent; // e.g., $i ref in pred - this.isEpsilon = true; - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return false; - } - - getPredicate() { - return new Predicate(this.ruleIndex, this.predIndex, this.isCtxDependent); - } - - toString() { - return "pred_" + this.ruleIndex + ":" + this.predIndex; - } -} diff --git a/src/atn/PredicateTransition.ts b/src/atn/PredicateTransition.ts new file mode 100644 index 0000000..68fe4ce --- /dev/null +++ b/src/atn/PredicateTransition.ts @@ -0,0 +1,43 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { SemanticContext } from "./SemanticContext.js"; +import { AbstractPredicateTransition } from "./AbstractPredicateTransition.js"; +import { TransitionType } from "./TransitionType.js"; +import { ATNState } from "./ATNState.js"; + +export class PredicateTransition extends AbstractPredicateTransition { + public readonly ruleIndex: number; + public readonly predIndex: number; + public readonly isCtxDependent: boolean; // e.g., $i ref in pred + + public constructor(target: ATNState, ruleIndex: number, predIndex: number, isCtxDependent: boolean) { + super(target); + this.ruleIndex = ruleIndex; + this.predIndex = predIndex; + this.isCtxDependent = isCtxDependent; // e.g., $i ref in pred + } + + public override get isEpsilon(): boolean { + return true; + } + + public matches(_symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return false; + } + + public get serializationType(): number { + return TransitionType.PREDICATE; + } + + public getPredicate(): SemanticContext.Predicate { + return new SemanticContext.Predicate(this.ruleIndex, this.predIndex, this.isCtxDependent); + } + + public override toString(): string { + return "pred_" + this.ruleIndex + ":" + this.predIndex; + } +} diff --git a/src/atn/PredictionContext.js b/src/atn/PredictionContext.js deleted file mode 100644 index 91d5a4f..0000000 --- a/src/atn/PredictionContext.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class PredictionContext { - constructor(cachedHashCode) { - this.cachedHashCode = cachedHashCode; - } - - /** - * Stores the computed hash code of this {@link PredictionContext}. The hash - * code is computed in parts to match the following reference algorithm. - * - *
            -     * private int referenceHashCode() {
            -     * int hash = {@link MurmurHash//initialize MurmurHash.initialize}({@link
            -     * //INITIAL_HASH});
            -     *
            -     * for (int i = 0; i < {@link //size()}; i++) {
            -     * hash = {@link MurmurHash//update MurmurHash.update}(hash, {@link //getParent
            -     * getParent}(i));
            -     * }
            -     *
            -     * for (int i = 0; i < {@link //size()}; i++) {
            -     * hash = {@link MurmurHash//update MurmurHash.update}(hash, {@link
            -     * //getReturnState getReturnState}(i));
            -     * }
            -     *
            -     * hash = {@link MurmurHash//finish MurmurHash.finish}(hash, 2// {@link
            -     * //size()});
            -     * return hash;
            -     * }
            -     * 
            - * This means only the {@link //EMPTY} context is in set. - */ - isEmpty() { - return this === PredictionContext.EMPTY; - } - - hasEmptyPath() { - return this.getReturnState(this.length - 1) === PredictionContext.EMPTY_RETURN_STATE; - } - - hashCode() { - return this.cachedHashCode; - } - - updateHashCode(hash) { - hash.update(this.cachedHashCode); - } -} - -/** - * Represents {@code $} in local context prediction, which means wildcard. - * {@code//+x =//}. - */ -PredictionContext.EMPTY = null; - -/** - * Represents {@code $} in an array in full context mode, when {@code $} - * doesn't mean wildcard: {@code $ + x = [$,x]}. Here, - * {@code $} = {@link //EMPTY_RETURN_STATE}. - */ -PredictionContext.EMPTY_RETURN_STATE = 0x7FFFFFFF; - -PredictionContext.globalNodeCount = 1; -PredictionContext.id = PredictionContext.globalNodeCount; -PredictionContext.trace_atn_sim = false; diff --git a/src/atn/PredictionContext.ts b/src/atn/PredictionContext.ts new file mode 100644 index 0000000..b1a155a --- /dev/null +++ b/src/atn/PredictionContext.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-returns, jsdoc/require-param */ + +import { Recognizer } from "../Recognizer.js"; +import { HashCode } from "../misc/HashCode.js"; +import { ATNSimulator } from "./ATNSimulator.js"; + +// Most of the implementation is located in PredictionContextUtils.ts, to avoid circular dependencies. + +export abstract class PredictionContext { + /** + * Represents {@code $} in an array in full context mode, when {@code $} + * doesn't mean wildcard: {@code $ + x = [$,x]}. Here, + * {@code $} = {@link EMPTY_RETURN_STATE}. + */ + public static readonly EMPTY_RETURN_STATE = 0x7FFFFFFF; + + // TODO: Temporarily here. Should be moved to EmptyPredictionContext. It's initialized in that context class. + public static EMPTY: PredictionContext; + + public static trace_atn_sim = false; + + private cachedHashCode: number; + + public constructor(cachedHashCode: number) { + this.cachedHashCode = cachedHashCode; + } + + public isEmpty(): boolean { + return false; + } + + public hasEmptyPath(): boolean { + return this.getReturnState(this.length - 1) === PredictionContext.EMPTY_RETURN_STATE; + } + + public hashCode(): number { + return this.cachedHashCode; + } + + public updateHashCode(hash: HashCode): void { + hash.update(this.cachedHashCode); + } + + public toString(_recog?: Recognizer): string { + return ""; + } + + public abstract getParent(index: number): PredictionContext | null; + public abstract getReturnState(index: number): number; + public abstract get length(): number; + public abstract equals(obj: unknown): boolean; +} diff --git a/src/atn/PredictionContextCache.d.ts b/src/atn/PredictionContextCache.d.ts deleted file mode 100644 index c68f64f..0000000 --- a/src/atn/PredictionContextCache.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class PredictionContextCache { -} diff --git a/src/atn/PredictionContextCache.js b/src/atn/PredictionContextCache.ts similarity index 80% rename from src/atn/PredictionContextCache.js rename to src/atn/PredictionContextCache.ts index 422a8e2..2e489f5 100644 --- a/src/atn/PredictionContextCache.js +++ b/src/atn/PredictionContextCache.ts @@ -13,17 +13,17 @@ import { HashMap } from "../misc/HashMap.js"; * can be used for both lexers and parsers. */ export class PredictionContextCache { - - constructor() { - this.cache = new HashMap(); - } + private cache = new HashMap(); /** * Add a context to the cache and return it. If the context already exists, * return that one instead and do not add a new context to the cache. * Protect shared cache from unsafe thread access. + * + * @param ctx tbd + * @returns tbd */ - add(ctx) { + public add(ctx: PredictionContext): PredictionContext { if (ctx === PredictionContext.EMPTY) { return PredictionContext.EMPTY; } @@ -32,14 +32,15 @@ export class PredictionContextCache { return existing; } this.cache.set(ctx, ctx); + return ctx; } - get(ctx) { + public get(ctx: PredictionContext): PredictionContext | null { return this.cache.get(ctx) || null; } - get length() { + public get length(): number { return this.cache.length; } } diff --git a/src/atn/PredictionContextUtils.js b/src/atn/PredictionContextUtils.ts similarity index 72% rename from src/atn/PredictionContextUtils.js rename to src/atn/PredictionContextUtils.ts index fd7c3aa..13afea5 100644 --- a/src/atn/PredictionContextUtils.js +++ b/src/atn/PredictionContextUtils.ts @@ -4,34 +4,43 @@ * can be found in the LICENSE.txt file in the project root. */ -import { RuleContext } from "./RuleContext.js"; +/* eslint-disable jsdoc/require-returns, jsdoc/require-param */ + +import { RuleContext } from "../RuleContext.js"; import { PredictionContext } from "./PredictionContext.js"; import { ArrayPredictionContext } from "./ArrayPredictionContext.js"; import { SingletonPredictionContext } from "./SingletonPredictionContext.js"; import { EmptyPredictionContext } from "./EmptyPredictionContext.js"; import { HashMap } from "../misc/HashMap.js"; +import { ATN } from "./ATN.js"; +import { PredictionContextCache } from "./PredictionContextCache.js"; +import { DoubleDict } from "../utils/DoubleDict.js"; +import { ParserRuleContext } from "../ParserRuleContext.js"; +import { RuleTransition } from "./RuleTransition.js"; /** * Convert a {@link RuleContext} tree to a {@link PredictionContext} graph. - * Return {@link //EMPTY} if {@code outerContext} is empty or null. + * Return {@link EMPTY} if {@code outerContext} is empty or null. */ -export function predictionContextFromRuleContext(atn, outerContext) { +export const predictionContextFromRuleContext = (atn: ATN, outerContext: RuleContext): PredictionContext => { if (outerContext === undefined || outerContext === null) { - outerContext = RuleContext.EMPTY; + outerContext = ParserRuleContext.EMPTY; } // if we are in RuleContext of start rule, s, then PredictionContext // is EMPTY. Nobody called us. (if we are empty, return empty) - if (outerContext.parent === null || outerContext === RuleContext.EMPTY) { + if (outerContext.parent === null || outerContext === ParserRuleContext.EMPTY) { return PredictionContext.EMPTY; } // If we have a parent, convert it to a PredictionContext graph const parent = predictionContextFromRuleContext(atn, outerContext.parent); - const state = atn.states[outerContext.invokingState]; - const transition = state.transitions[0]; + const state = atn.states[outerContext.invokingState]!; + const transition = state.transitions[0] as RuleTransition; + return SingletonPredictionContext.create(parent, transition.followState.stateNumber); -} +}; -export function getCachedPredictionContext(context, contextCache, visited) { +export const getCachedPredictionContext = (context: PredictionContext, contextCache: PredictionContextCache, + visited: HashMap): PredictionContext => { if (context.isEmpty()) { return context; } @@ -42,12 +51,13 @@ export function getCachedPredictionContext(context, contextCache, visited) { existing = contextCache.get(context); if (existing !== null) { visited.set(context, existing); + return existing; } let changed = false; let parents = []; for (let i = 0; i < parents.length; i++) { - const parent = getCachedPredictionContext(context.getParent(i), contextCache, visited); + const parent = getCachedPredictionContext(context.getParent(i)!, contextCache, visited); if (changed || parent !== context.getParent(i)) { if (!changed) { parents = []; @@ -62,25 +72,26 @@ export function getCachedPredictionContext(context, contextCache, visited) { if (!changed) { contextCache.add(context); visited.set(context, context); + return context; } let updated = null; if (parents.length === 0) { updated = PredictionContext.EMPTY; } else if (parents.length === 1) { - updated = SingletonPredictionContext.create(parents[0], context - .getReturnState(0)); + updated = SingletonPredictionContext.create(parents[0], context.getReturnState(0)); } else { - updated = new ArrayPredictionContext(parents, context.returnStates); + updated = new ArrayPredictionContext(parents, (context as ArrayPredictionContext).returnStates); } contextCache.add(updated); visited.set(updated, updated); visited.set(context, updated); return updated; -} +}; -export function merge(a, b, rootIsWildcard, mergeCache) { +export const merge = (a: PredictionContext, b: PredictionContext, rootIsWildcard: boolean, + mergeCache: DoubleDict | null): PredictionContext => { // share same graph if both same if (a === b) { return a; @@ -100,13 +111,14 @@ export function merge(a, b, rootIsWildcard, mergeCache) { } // convert singleton so both are arrays to normalize if (a instanceof SingletonPredictionContext) { - a = new ArrayPredictionContext([a.getParent()], [a.returnState]); + a = new ArrayPredictionContext([a.parent], [a.returnState]); } if (b instanceof SingletonPredictionContext) { - b = new ArrayPredictionContext([b.getParent()], [b.returnState]); + b = new ArrayPredictionContext([b.parent], [b.returnState]); } - return mergeArrays(a, b, rootIsWildcard, mergeCache); -} + + return mergeArrays(a as ArrayPredictionContext, b as ArrayPredictionContext, rootIsWildcard, mergeCache); +}; /** * Merge two {@link ArrayPredictionContext} instances. @@ -128,16 +140,19 @@ export function merge(a, b, rootIsWildcard, mergeCache) { * {@link SingletonPredictionContext}.
            *

            */ -function mergeArrays(a, b, rootIsWildcard, mergeCache) { +const mergeArrays = (a: ArrayPredictionContext, b: ArrayPredictionContext, rootIsWildcard: boolean, + mergeCache: DoubleDict | null): PredictionContext => { if (mergeCache !== null) { let previous = mergeCache.get(a, b); if (previous !== null) { - if (PredictionContext.trace_atn_sim) console.log("mergeArrays a=" + a + ",b=" + b + " -> previous"); + if (PredictionContext.trace_atn_sim) { console.log("mergeArrays a=" + a + ",b=" + b + " -> previous"); } + return previous; } previous = mergeCache.get(b, a); if (previous !== null) { - if (PredictionContext.trace_atn_sim) console.log("mergeArrays a=" + a + ",b=" + b + " -> previous"); + if (PredictionContext.trace_atn_sim) { console.log("mergeArrays a=" + a + ",b=" + b + " -> previous"); } + return previous; } } @@ -146,36 +161,36 @@ function mergeArrays(a, b, rootIsWildcard, mergeCache) { let j = 0; // walks b let k = 0; // walks target M array - let mergedReturnStates = new Array(a.returnStates.length + b.returnStates.length).fill(0); - let mergedParents = new Array(a.returnStates.length + b.returnStates.length).fill(null); + let mergedReturnStates = new Array(a.returnStates.length + b.returnStates.length).fill(0); + let mergedParents = new Array(a.returnStates.length + b.returnStates.length).fill(null); // walk and merge to yield mergedParents, mergedReturnStates while (i < a.returnStates.length && j < b.returnStates.length) { - const a_parent = a.parents[i]; - const b_parent = b.parents[j]; + const aParent = a.parents[i]; + const bParent = b.parents[j]; if (a.returnStates[i] === b.returnStates[j]) { // same payload (stack tops are equal), must yield merged singleton const payload = a.returnStates[i]; // $+$ = $ const bothDollars = payload === PredictionContext.EMPTY_RETURN_STATE && - a_parent === null && b_parent === null; - const ax_ax = (a_parent !== null && b_parent !== null && a_parent === b_parent); // ax+ax + aParent === null && bParent === null; + const axAx = (aParent !== null && bParent !== null && aParent === bParent); // ax+ax // -> // ax - if (bothDollars || ax_ax) { - mergedParents[k] = a_parent; // choose left + if (bothDollars || axAx) { + mergedParents[k] = aParent; // choose left mergedReturnStates[k] = payload; } else { // ax+ay -> a'[x,y] - mergedParents[k] = merge(a_parent, b_parent, rootIsWildcard, mergeCache); + mergedParents[k] = merge(aParent!, bParent!, rootIsWildcard, mergeCache); mergedReturnStates[k] = payload; } i += 1; // hop over left one as usual j += 1; // but also skip one in right side since we merge } else if (a.returnStates[i] < b.returnStates[j]) { // copy a[i] to M - mergedParents[k] = a_parent; + mergedParents[k] = aParent; mergedReturnStates[k] = a.returnStates[i]; i += 1; } else { // b > a, copy b[j] to M - mergedParents[k] = b_parent; + mergedParents[k] = bParent; mergedReturnStates[k] = b.returnStates[j]; j += 1; } @@ -198,63 +213,68 @@ function mergeArrays(a, b, rootIsWildcard, mergeCache) { // trim merged if we combined a few that had same stack tops if (k < mergedParents.length) { // write index < last position; trim if (k === 1) { // for just one merged element, return singleton top - const a_ = SingletonPredictionContext.create(mergedParents[0], - mergedReturnStates[0]); + const aNew = SingletonPredictionContext.create(mergedParents[0], mergedReturnStates[0]); if (mergeCache !== null) { - mergeCache.set(a, b, a_); + mergeCache.set(a, b, aNew); } - return a_; + + return aNew; } mergedParents = mergedParents.slice(0, k); mergedReturnStates = mergedReturnStates.slice(0, k); } - const M = new ArrayPredictionContext(mergedParents, mergedReturnStates); + const merged = new ArrayPredictionContext(mergedParents, mergedReturnStates); // if we created same array as a or b, return that instead // TODO: track whether this is possible above during merge sort for speed - if (M.equals(a)) { + if (merged.equals(a)) { if (mergeCache !== null) { mergeCache.set(a, b, a); } - if (PredictionContext.trace_atn_sim) console.log("mergeArrays a=" + a + ",b=" + b + " -> a"); + if (PredictionContext.trace_atn_sim) { console.log("mergeArrays a=" + a + ",b=" + b + " -> a"); } + return a; } - if (M.equals(b)) { + if (merged.equals(b)) { if (mergeCache !== null) { mergeCache.set(a, b, b); } - if (PredictionContext.trace_atn_sim) console.log("mergeArrays a=" + a + ",b=" + b + " -> b"); + if (PredictionContext.trace_atn_sim) { console.log("mergeArrays a=" + a + ",b=" + b + " -> b"); } + return b; } combineCommonParents(mergedParents); if (mergeCache !== null) { - mergeCache.set(a, b, M); + mergeCache.set(a, b, merged); } - if (PredictionContext.trace_atn_sim) console.log("mergeArrays a=" + a + ",b=" + b + " -> " + M); + if (PredictionContext.trace_atn_sim) { console.log("mergeArrays a=" + a + ",b=" + b + " -> " + merged); } - return M; -} + return merged; +}; /** * Make pass over all M {@code parents}; merge any {@code equals()} * ones. */ -function combineCommonParents(parents) { - const uniqueParents = new HashMap(); +export const combineCommonParents = (parents: Array): void => { + const uniqueParents = new HashMap(); - for (let p = 0; p < parents.length; p++) { - const parent = parents[p]; - if (!(uniqueParents.containsKey(parent))) { - uniqueParents.set(parent, parent); + for (const parent of parents) { + if (parent) { + if (!(uniqueParents.containsKey(parent))) { + uniqueParents.set(parent, parent); + } } } for (let q = 0; q < parents.length; q++) { - parents[q] = uniqueParents.get(parents[q]); + if (parents[q]) { + parents[q] = uniqueParents.get(parents[q]!); + } } -} +}; /** * Merge two {@link SingletonPredictionContext} instances. @@ -285,9 +305,11 @@ function combineCommonParents(parents) { * @param b the second {@link SingletonPredictionContext} * @param rootIsWildcard {@code true} if this is a local-context merge, * otherwise false to indicate a full-context merge - * @param mergeCache + * @param mergeCache tbd */ -function mergeSingletons(a, b, rootIsWildcard, mergeCache) { +export const mergeSingletons = (a: SingletonPredictionContext, b: SingletonPredictionContext, + rootIsWildcard: boolean, + mergeCache: DoubleDict | null): PredictionContext => { if (mergeCache !== null) { let previous = mergeCache.get(a, b); if (previous !== null) { @@ -304,10 +326,11 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { if (mergeCache !== null) { mergeCache.set(a, b, rootMerge); } + return rootMerge; } if (a.returnState === b.returnState) { - const parent = merge(a.parent, b.parent, rootIsWildcard, mergeCache); + const parent = merge(a.parent!, b.parent!, rootIsWildcard, mergeCache); // if parent is same as existing a or b parent or reduced to a parent, // return it if (parent === a.parent) { @@ -324,6 +347,7 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { if (mergeCache !== null) { mergeCache.set(a, b, spc); } + return spc; } else { // a != b payloads differ // see if we can collapse parents due to $+x parents if local ctx @@ -345,6 +369,7 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { if (mergeCache !== null) { mergeCache.set(a, b, apc); } + return apc; } // parents differ and can't merge them. Just pack together @@ -357,28 +382,29 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { payloads[1] = a.returnState; parents = [b.parent, a.parent]; } - const a_ = new ArrayPredictionContext(parents, payloads); + const aNew = new ArrayPredictionContext(parents, payloads); if (mergeCache !== null) { - mergeCache.set(a, b, a_); + mergeCache.set(a, b, aNew); } - return a_; + + return aNew; } -} +}; /** * Handle case where at least one of {@code a} or {@code b} is - * {@link //EMPTY}. In the following diagrams, the symbol {@code $} is used - * to represent {@link //EMPTY}. + * {@link EMPTY}. In the following diagrams, the symbol {@code $} is used + * to represent {@link EMPTY}. * *

            Local-Context Merges

            * *

            These local-context merge operations are used when {@code rootIsWildcard} * is true.

            * - *

            {@link //EMPTY} is superset of any graph; return {@link //EMPTY}.
            + *

            {@link EMPTY} is superset of any graph; return {@link EMPTY}.
            *

            * - *

            {@link //EMPTY} and anything is {@code //EMPTY}, so merged parent is + *

            {@link EMPTY} and anything is {@code //EMPTY}, so merged parent is * {@code //EMPTY}; return left graph.
            *

            * @@ -392,7 +418,7 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { * *

            * - *

            Must keep all contexts; {@link //EMPTY} in array is a special value (and + *

            Must keep all contexts; {@link EMPTY} in array is a special value (and * null parent).
            *

            * @@ -403,7 +429,8 @@ function mergeSingletons(a, b, rootIsWildcard, mergeCache) { * @param rootIsWildcard {@code true} if this is a local-context merge, * otherwise false to indicate a full-context merge */ -function mergeRoot(a, b, rootIsWildcard) { +export const mergeRoot = (a: SingletonPredictionContext, b: SingletonPredictionContext, + rootIsWildcard: boolean): PredictionContext | null => { if (rootIsWildcard) { if (a === PredictionContext.EMPTY) { return PredictionContext.EMPTY; // // + b =// @@ -418,23 +445,29 @@ function mergeRoot(a, b, rootIsWildcard) { const payloads = [b.returnState, PredictionContext.EMPTY_RETURN_STATE]; const parents = [b.parent, null]; + return new ArrayPredictionContext(parents, payloads); } else if (b === PredictionContext.EMPTY) { // x + $ = [$,x] ($ is always first if present) const payloads = [a.returnState, PredictionContext.EMPTY_RETURN_STATE]; const parents = [a.parent, null]; + return new ArrayPredictionContext(parents, payloads); } } + return null; -} +}; // ter's recursive version of Sam's getAllNodes() -export function getAllContextNodes(context, nodes, visited) { +export const getAllContextNodes = (context: PredictionContext | null, nodes: PredictionContext[], + visited: HashMap | null): PredictionContext[] => { if (nodes === null) { nodes = []; + return getAllContextNodes(context, nodes, visited); } else if (visited === null) { visited = new HashMap(); + return getAllContextNodes(context, nodes, visited); } else { if (context === null || visited.containsKey(context)) { @@ -445,6 +478,7 @@ export function getAllContextNodes(context, nodes, visited) { for (let i = 0; i < context.length; i++) { getAllContextNodes(context.getParent(i), nodes, visited); } + return nodes; } -} +}; diff --git a/src/atn/PredictionMode.d.ts b/src/atn/PredictionMode.d.ts deleted file mode 100644 index 0a3b7c3..0000000 --- a/src/atn/PredictionMode.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/* eslint-disable @typescript-eslint/naming-convention */ - -export declare class PredictionMode { - public static SLL: number; - public static LL: number; - public static LL_EXACT_AMBIG_DETECTION: number; -} diff --git a/src/atn/PredictionMode.js b/src/atn/PredictionMode.js deleted file mode 100644 index 662e6c8..0000000 --- a/src/atn/PredictionMode.js +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATN } from './ATN.js'; -import { RuleStopState } from './RuleStopState.js'; -import { ATNConfigSet } from './ATNConfigSet.js'; -import { ATNConfig } from './ATNConfig.js'; -import { SemanticContext } from './SemanticContext.js'; -import { BitSet } from "../misc/BitSet.js"; -import { AltDict } from "../misc/AltDict.js"; -import { HashCode } from "../misc/HashCode.js"; -import { HashMap } from "../misc/HashMap.js"; - -/** - * This enumeration defines the prediction modes available in ANTLR 4 along with - * utility methods for analyzing configuration sets for conflicts and/or - * ambiguities. - */ -export const PredictionMode = { - /** - * The SLL(*) prediction mode. This prediction mode ignores the current - * parser context when making predictions. This is the fastest prediction - * mode, and provides correct results for many grammars. This prediction - * mode is more powerful than the prediction mode provided by ANTLR 3, but - * may result in syntax errors for grammar and input combinations which are - * not SLL. - * - *

            - * When using this prediction mode, the parser will either return a correct - * parse tree (i.e. the same parse tree that would be returned with the - * {@link //LL} prediction mode), or it will report a syntax error. If a - * syntax error is encountered when using the {@link //SLL} prediction mode, - * it may be due to either an actual syntax error in the input or indicate - * that the particular combination of grammar and input requires the more - * powerful {@link //LL} prediction abilities to complete successfully.

            - * - *

            - * This prediction mode does not provide any guarantees for prediction - * behavior for syntactically-incorrect inputs.

            - */ - SLL: 0, - - /** - * The LL(*) prediction mode. This prediction mode allows the current parser - * context to be used for resolving SLL conflicts that occur during - * prediction. This is the fastest prediction mode that guarantees correct - * parse results for all combinations of grammars with syntactically correct - * inputs. - * - *

            - * When using this prediction mode, the parser will make correct decisions - * for all syntactically-correct grammar and input combinations. However, in - * cases where the grammar is truly ambiguous this prediction mode might not - * report a precise answer for exactly which alternatives are - * ambiguous.

            - * - *

            - * This prediction mode does not provide any guarantees for prediction - * behavior for syntactically-incorrect inputs.

            - */ - LL: 1, - - /** - * - * The LL(*) prediction mode with exact ambiguity detection. In addition to - * the correctness guarantees provided by the {@link //LL} prediction mode, - * this prediction mode instructs the prediction algorithm to determine the - * complete and exact set of ambiguous alternatives for every ambiguous - * decision encountered while parsing. - * - *

            - * This prediction mode may be used for diagnosing ambiguities during - * grammar development. Due to the performance overhead of calculating sets - * of ambiguous alternatives, this prediction mode should be avoided when - * the exact results are not necessary.

            - * - *

            - * This prediction mode does not provide any guarantees for prediction - * behavior for syntactically-incorrect inputs.

            - */ - LL_EXACT_AMBIG_DETECTION: 2, - - /** - * - * Computes the SLL prediction termination condition. - * - *

            - * This method computes the SLL prediction termination condition for both of - * the following cases.

            - * - *
              - *
            • The usual SLL+LL fallback upon SLL conflict
            • - *
            • Pure SLL without LL fallback
            • - *
            - * - *

            COMBINED SLL+LL PARSING

            - * - *

            When LL-fallback is enabled upon SLL conflict, correct predictions are - * ensured regardless of how the termination condition is computed by this - * method. Due to the substantially higher cost of LL prediction, the - * prediction should only fall back to LL when the additional lookahead - * cannot lead to a unique SLL prediction.

            - * - *

            Assuming combined SLL+LL parsing, an SLL configuration set with only - * conflicting subsets should fall back to full LL, even if the - * configuration sets don't resolve to the same alternative (e.g. - * {@code {1,2}} and {@code {3,4}}. If there is at least one non-conflicting - * configuration, SLL could continue with the hopes that more lookahead will - * resolve via one of those non-conflicting configurations.

            - * - *

            Here's the prediction termination rule them: SLL (for SLL+LL parsing) - * stops when it sees only conflicting configuration subsets. In contrast, - * full LL keeps going when there is uncertainty.

            - * - *

            HEURISTIC

            - * - *

            As a heuristic, we stop prediction when we see any conflicting subset - * unless we see a state that only has one alternative associated with it. - * The single-alt-state thing lets prediction continue upon rules like - * (otherwise, it would admit defeat too soon):

            - * - *

            {@code [12|1|[], 6|2|[], 12|2|[]]. s : (ID | ID ID?) ';' ;}

            - * - *

            When the ATN simulation reaches the state before {@code ';'}, it has a - * DFA state that looks like: {@code [12|1|[], 6|2|[], 12|2|[]]}. Naturally - * {@code 12|1|[]} and {@code 12|2|[]} conflict, but we cannot stop - * processing this node because alternative to has another way to continue, - * via {@code [6|2|[]]}.

            - * - *

            It also let's us continue for this rule:

            - * - *

            {@code [1|1|[], 1|2|[], 8|3|[]] a : A | A | A B ;}

            - * - *

            After matching input A, we reach the stop state for rule A, state 1. - * State 8 is the state right before B. Clearly alternatives 1 and 2 - * conflict and no amount of further lookahead will separate the two. - * However, alternative 3 will be able to continue and so we do not stop - * working on this state. In the previous example, we're concerned with - * states associated with the conflicting alternatives. Here alt 3 is not - * associated with the conflicting configs, but since we can continue - * looking for input reasonably, don't declare the state done.

            - * - *

            PURE SLL PARSING

            - * - *

            To handle pure SLL parsing, all we have to do is make sure that we - * combine stack contexts for configurations that differ only by semantic - * predicate. From there, we can do the usual SLL termination heuristic.

            - * - *

            PREDICATES IN SLL+LL PARSING

            - * - *

            SLL decisions don't evaluate predicates until after they reach DFA stop - * states because they need to create the DFA cache that works in all - * semantic situations. In contrast, full LL evaluates predicates collected - * during start state computation so it can ignore predicates thereafter. - * This means that SLL termination detection can totally ignore semantic - * predicates.

            - * - *

            Implementation-wise, {@link ATNConfigSet} combines stack contexts but not - * semantic predicate contexts so we might see two configurations like the - * following.

            - * - *

            {@code (s, 1, x, {}), (s, 1, x', {p})}

            - * - *

            Before testing these configurations against others, we have to merge - * {@code x} and {@code x'} (without modifying the existing configurations). - * For example, we test {@code (x+x')==x''} when looking for conflicts in - * the following configurations.

            - * - *

            {@code (s, 1, x, {}), (s, 1, x', {p}), (s, 2, x'', {})}

            - * - *

            If the configuration set has predicates (as indicated by - * {@link ATNConfigSet//hasSemanticContext}), this algorithm makes a copy of - * the configurations to strip out all of the predicates so that a standard - * {@link ATNConfigSet} will merge everything ignoring predicates.

            - */ - hasSLLConflictTerminatingPrediction: function (mode, configs) { - // Configs in rule stop states indicate reaching the end of the decision - // rule (local context) or end of start rule (full context). If all - // configs meet this condition, then none of the configurations is able - // to match additional input so we terminate prediction. - // - if (PredictionMode.allConfigsInRuleStopStates(configs)) { - return true; - } - // pure SLL mode parsing - if (mode === PredictionMode.SLL) { - // Don't bother with combining configs from different semantic - // contexts if we can fail over to full LL; costs more time - // since we'll often fail over anyway. - if (configs.hasSemanticContext) { - // dup configs, tossing out semantic predicates - const dup = new ATNConfigSet(); - for (let i = 0; i < configs.items.length; i++) { - let c = configs.items[i]; - c = new ATNConfig({ semanticContext: SemanticContext.NONE }, c); - dup.add(c); - } - configs = dup; - } - // now we have combined contexts for configs with dissimilar preds - } - // pure SLL or combined SLL+LL mode parsing - const altsets = PredictionMode.getConflictingAltSubsets(configs); - return PredictionMode.hasConflictingAltSet(altsets) && !PredictionMode.hasStateAssociatedWithOneAlt(configs); - }, - - /** - * Checks if any configuration in {@code configs} is in a - * {@link RuleStopState}. Configurations meeting this condition have reached - * the end of the decision rule (local context) or end of start rule (full - * context). - * - * @param configs the configuration set to test - * @return {@code true} if any configuration in {@code configs} is in a - * {@link RuleStopState}, otherwise {@code false} - */ - hasConfigInRuleStopState: function (configs) { - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; - if (c.state instanceof RuleStopState) { - return true; - } - } - return false; - }, - - /** - * Checks if all configurations in {@code configs} are in a - * {@link RuleStopState}. Configurations meeting this condition have reached - * the end of the decision rule (local context) or end of start rule (full - * context). - * - * @param configs the configuration set to test - * @return {@code true} if all configurations in {@code configs} are in a - * {@link RuleStopState}, otherwise {@code false} - */ - allConfigsInRuleStopStates: function (configs) { - for (let i = 0; i < configs.items.length; i++) { - const c = configs.items[i]; - if (!(c.state instanceof RuleStopState)) { - return false; - } - } - return true; - }, - - /** - * - * Full LL prediction termination. - * - *

            Can we stop looking ahead during ATN simulation or is there some - * uncertainty as to which alternative we will ultimately pick, after - * consuming more input? Even if there are partial conflicts, we might know - * that everything is going to resolve to the same minimum alternative. That - * means we can stop since no more lookahead will change that fact. On the - * other hand, there might be multiple conflicts that resolve to different - * minimums. That means we need more look ahead to decide which of those - * alternatives we should predict.

            - * - *

            The basic idea is to split the set of configurations {@code C}, into - * conflicting subsets {@code (s, _, ctx, _)} and singleton subsets with - * non-conflicting configurations. Two configurations conflict if they have - * identical {@link ATNConfig//state} and {@link ATNConfig//context} values - * but different {@link ATNConfig//alt} value, e.g. {@code (s, i, ctx, _)} - * and {@code (s, j, ctx, _)} for {@code i!=j}.

            - * - *

            Reduce these configuration subsets to the set of possible alternatives. - * You can compute the alternative subsets in one pass as follows:

            - * - *

            {@code A_s,ctx = {i | (s, i, ctx, _)}} for each configuration in - * {@code C} holding {@code s} and {@code ctx} fixed.

            - * - *

            Or in pseudo-code, for each configuration {@code c} in {@code C}:

            - * - *
            -     * map[c] U= c.{@link ATNConfig//alt alt} // map hash/equals uses s and x, not
            -     * alt and not pred
            -     * 
            - * - *

            The values in {@code map} are the set of {@code A_s,ctx} sets.

            - * - *

            If {@code |A_s,ctx|=1} then there is no conflict associated with - * {@code s} and {@code ctx}.

            - * - *

            Reduce the subsets to singletons by choosing a minimum of each subset. If - * the union of these alternative subsets is a singleton, then no amount of - * more lookahead will help us. We will always pick that alternative. If, - * however, there is more than one alternative, then we are uncertain which - * alternative to predict and must continue looking for resolution. We may - * or may not discover an ambiguity in the future, even if there are no - * conflicting subsets this round.

            - * - *

            The biggest sin is to terminate early because it means we've made a - * decision but were uncertain as to the eventual outcome. We haven't used - * enough lookahead. On the other hand, announcing a conflict too late is no - * big deal; you will still have the conflict. It's just inefficient. It - * might even look until the end of file.

            - * - *

            No special consideration for semantic predicates is required because - * predicates are evaluated on-the-fly for full LL prediction, ensuring that - * no configuration contains a semantic context during the termination - * check.

            - * - *

            CONFLICTING CONFIGS

            - * - *

            Two configurations {@code (s, i, x)} and {@code (s, j, x')}, conflict - * when {@code i!=j} but {@code x=x'}. Because we merge all - * {@code (s, i, _)} configurations together, that means that there are at - * most {@code n} configurations associated with state {@code s} for - * {@code n} possible alternatives in the decision. The merged stacks - * complicate the comparison of configuration contexts {@code x} and - * {@code x'}. Sam checks to see if one is a subset of the other by calling - * merge and checking to see if the merged result is either {@code x} or - * {@code x'}. If the {@code x} associated with lowest alternative {@code i} - * is the superset, then {@code i} is the only possible prediction since the - * others resolve to {@code min(i)} as well. However, if {@code x} is - * associated with {@code j>i} then at least one stack configuration for - * {@code j} is not in conflict with alternative {@code i}. The algorithm - * should keep going, looking for more lookahead due to the uncertainty.

            - * - *

            For simplicity, I'm doing a equality check between {@code x} and - * {@code x'} that lets the algorithm continue to consume lookahead longer - * than necessary. The reason I like the equality is of course the - * simplicity but also because that is the test you need to detect the - * alternatives that are actually in conflict.

            - * - *

            CONTINUE/STOP RULE

            - * - *

            Continue if union of resolved alternative sets from non-conflicting and - * conflicting alternative subsets has more than one alternative. We are - * uncertain about which alternative to predict.

            - * - *

            The complete set of alternatives, {@code [i for (_,i,_)]}, tells us which - * alternatives are still in the running for the amount of input we've - * consumed at this point. The conflicting sets let us to strip away - * configurations that won't lead to more states because we resolve - * conflicts to the configuration with a minimum alternate for the - * conflicting set.

            - * - *

            CASES

            - * - *
              - * - *
            • no conflicts and more than 1 alternative in set => continue
            • - * - *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s, 3, z)}, - * {@code (s', 1, y)}, {@code (s', 2, y)} yields non-conflicting set - * {@code {3}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} = - * {@code {1,3}} => continue - *
            • - * - *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)}, - * {@code (s', 2, y)}, {@code (s'', 1, z)} yields non-conflicting set - * {@code {1}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} = - * {@code {1}} => stop and predict 1
            • - * - *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)}, - * {@code (s', 2, y)} yields conflicting, reduced sets {@code {1}} U - * {@code {1}} = {@code {1}} => stop and predict 1, can announce - * ambiguity {@code {1,2}}
            • - * - *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 2, y)}, - * {@code (s', 3, y)} yields conflicting, reduced sets {@code {1}} U - * {@code {2}} = {@code {1,2}} => continue
            • - * - *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 3, y)}, - * {@code (s', 4, y)} yields conflicting, reduced sets {@code {1}} U - * {@code {3}} = {@code {1,3}} => continue
            • - * - *
            - * - *

            EXACT AMBIGUITY DETECTION

            - * - *

            If all states report the same conflicting set of alternatives, then we - * know we have the exact ambiguity set.

            - * - *

            |A_i|>1 and - * A_i = A_j for all i, j.

            - * - *

            In other words, we continue examining lookahead until all {@code A_i} - * have more than one alternative and all {@code A_i} are the same. If - * {@code A={{1,2}, {1,3}}}, then regular LL prediction would terminate - * because the resolved set is {@code {1}}. To determine what the real - * ambiguity is, we have to know whether the ambiguity is between one and - * two or one and three so we keep going. We can only stop prediction when - * we need exact ambiguity detection when the sets look like - * {@code A={{1,2}}} or {@code {{1,2},{1,2}}}, etc...

            - */ - resolvesToJustOneViableAlt: function (altsets) { - return PredictionMode.getSingleViableAlt(altsets); - }, - - /** - * Determines if every alternative subset in {@code altsets} contains more - * than one alternative. - * - * @param altsets a collection of alternative subsets - * @return {@code true} if every {@link BitSet} in {@code altsets} has - * {@link BitSet//cardinality cardinality} > 1, otherwise {@code false} - */ - allSubsetsConflict: function (altsets) { - return !PredictionMode.hasNonConflictingAltSet(altsets); - }, - /** - * Determines if any single alternative subset in {@code altsets} contains - * exactly one alternative. - * - * @param altsets a collection of alternative subsets - * @return {@code true} if {@code altsets} contains a {@link BitSet} with - * {@link BitSet//cardinality cardinality} 1, otherwise {@code false} - */ - hasNonConflictingAltSet: function (altsets) { - for (let i = 0; i < altsets.length; i++) { - const alts = altsets[i]; - if (alts.length === 1) { - return true; - } - } - return false; - }, - - - /** - * Determines if any single alternative subset in {@code altsets} contains - * more than one alternative. - * - * @param altsets a collection of alternative subsets - * @return {@code true} if {@code altsets} contains a {@link BitSet} with - * {@link BitSet//cardinality cardinality} > 1, otherwise {@code false} - */ - hasConflictingAltSet: function (altsets) { - for (let i = 0; i < altsets.length; i++) { - const alts = altsets[i]; - if (alts.length > 1) { - return true; - } - } - return false; - }, - - - /** - * Determines if every alternative subset in {@code altsets} is equivalent. - * - * @param altsets a collection of alternative subsets - * @return {@code true} if every member of {@code altsets} is equal to the - * others, otherwise {@code false} - */ - allSubsetsEqual: function (altsets) { - let first = null; - for (let i = 0; i < altsets.length; i++) { - const alts = altsets[i]; - if (first === null) { - first = alts; - } else if (alts !== first) { - return false; - } - } - return true; - }, - - - /** - * Returns the unique alternative predicted by all alternative subsets in - * {@code altsets}. If no such alternative exists, this method returns - * {@link ATN//INVALID_ALT_NUMBER}. - * - * @param altsets a collection of alternative subsets - */ - getUniqueAlt: function (altsets) { - const all = PredictionMode.getAlts(altsets); - if (all.length === 1) { - return all.nextSetBit(0); - } else { - return ATN.INVALID_ALT_NUMBER; - } - }, - - /** - * Gets the complete set of represented alternatives for a collection of - * alternative subsets. This method returns the union of each {@link BitSet} - * in {@code altsets}. - * - * @param altsets a collection of alternative subsets - * @return the set of represented alternatives in {@code altsets} - */ - getAlts: function (altsets) { - const all = new BitSet(); - altsets.map(function (alts) { all.or(alts); }); - return all; - }, - - /** - * This function gets the conflicting alt subsets from a configuration set. - * For each configuration {@code c} in {@code configs}: - * - *
            -     * map[c] U= c.{@link ATNConfig//alt alt} // map hash/equals uses s and x, not
            -     * alt and not pred
            -     * 
            - */ - getConflictingAltSubsets: function (configs) { - const configToAlts = new HashMap(); - configToAlts.hashFunction = function (cfg) { HashCode.hashStuff(cfg.state.stateNumber, cfg.context); }; - configToAlts.equalsFunction = function (c1, c2) { return c1.state.stateNumber === c2.state.stateNumber && c1.context.equals(c2.context); }; - configs.items.map(function (cfg) { - let alts = configToAlts.get(cfg); - if (alts === null) { - alts = new BitSet(); - configToAlts.set(cfg, alts); - } - alts.set(cfg.alt); - }); - return configToAlts.getValues(); - }, - - /** - * Get a map from state to alt subset from a configuration set. For each - * configuration {@code c} in {@code configs}: - * - *
            -     * map[c.{@link ATNConfig//state state}] U= c.{@link ATNConfig//alt alt}
            -     * 
            - */ - getStateToAltMap: function (configs) { - const m = new AltDict(); - configs.items.map(function (c) { - let alts = m.get(c.state); - if (alts === null) { - alts = new BitSet(); - m.set(c.state, alts); - } - alts.set(c.alt); - }); - return m; - }, - - hasStateAssociatedWithOneAlt: function (configs) { - const values = PredictionMode.getStateToAltMap(configs).values(); - for (let i = 0; i < values.length; i++) { - if (values[i].length === 1) { - return true; - } - } - return false; - }, - - getSingleViableAlt: function (altsets) { - let result = null; - for (let i = 0; i < altsets.length; i++) { - const alts = altsets[i]; - const minAlt = alts.nextSetBit(0); - if (result === null) { - result = minAlt; - } else if (result !== minAlt) { // more than 1 viable alt - return ATN.INVALID_ALT_NUMBER; - } - } - return result; - } -}; diff --git a/src/atn/PredictionMode.ts b/src/atn/PredictionMode.ts new file mode 100644 index 0000000..42ad410 --- /dev/null +++ b/src/atn/PredictionMode.ts @@ -0,0 +1,577 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-returns, jsdoc/require-param */ + +import { ATN } from "./ATN.js"; +import { RuleStopState } from "./RuleStopState.js"; +import { ATNConfigSet } from "./ATNConfigSet.js"; +import { ATNConfig } from "./ATNConfig.js"; +import { SemanticContext } from "./SemanticContext.js"; +import { BitSet } from "../misc/BitSet.js"; +import { HashCode } from "../misc/HashCode.js"; +import { HashMap } from "../misc/HashMap.js"; +import { ATNState } from "./ATNState.js"; + +/** + * This enumeration defines the prediction modes available in ANTLR 4 along with + * utility methods for analyzing configuration sets for conflicts and/or + * ambiguities. + */ +export class PredictionMode { + /** + * The SLL(*) prediction mode. This prediction mode ignores the current + * parser context when making predictions. This is the fastest prediction + * mode, and provides correct results for many grammars. This prediction + * mode is more powerful than the prediction mode provided by ANTLR 3, but + * may result in syntax errors for grammar and input combinations which are + * not SLL. + * + *

            + * When using this prediction mode, the parser will either return a correct + * parse tree (i.e. the same parse tree that would be returned with the + * {@link LL} prediction mode), or it will report a syntax error. If a + * syntax error is encountered when using the {@link SLL} prediction mode, + * it may be due to either an actual syntax error in the input or indicate + * that the particular combination of grammar and input requires the more + * powerful {@link LL} prediction abilities to complete successfully.

            + * + *

            + * This prediction mode does not provide any guarantees for prediction + * behavior for syntactically-incorrect inputs.

            + */ + public static readonly SLL: number = 0; + + /** + * The LL(*) prediction mode. This prediction mode allows the current parser + * context to be used for resolving SLL conflicts that occur during + * prediction. This is the fastest prediction mode that guarantees correct + * parse results for all combinations of grammars with syntactically correct + * inputs. + * + *

            + * When using this prediction mode, the parser will make correct decisions + * for all syntactically-correct grammar and input combinations. However, in + * cases where the grammar is truly ambiguous this prediction mode might not + * report a precise answer for exactly which alternatives are + * ambiguous.

            + * + *

            + * This prediction mode does not provide any guarantees for prediction + * behavior for syntactically-incorrect inputs.

            + */ + public static readonly LL: number = 1; + + /** + * + * The LL(*) prediction mode with exact ambiguity detection. In addition to + * the correctness guarantees provided by the {@link LL} prediction mode, + * this prediction mode instructs the prediction algorithm to determine the + * complete and exact set of ambiguous alternatives for every ambiguous + * decision encountered while parsing. + * + *

            + * This prediction mode may be used for diagnosing ambiguities during + * grammar development. Due to the performance overhead of calculating sets + * of ambiguous alternatives, this prediction mode should be avoided when + * the exact results are not necessary.

            + * + *

            + * This prediction mode does not provide any guarantees for prediction + * behavior for syntactically-incorrect inputs.

            + */ + public static readonly LL_EXACT_AMBIG_DETECTION: number = 2; + + /** + * + *Computes the SLL prediction termination condition. + * + *

            + *This method computes the SLL prediction termination condition for both of + *the following cases.

            + * + *
              + *
            • The usual SLL+LL fallback upon SLL conflict
            • + *
            • Pure SLL without LL fallback
            • + *
            + * + *

            COMBINED SLL+LL PARSING

            + * + *

            When LL-fallback is enabled upon SLL conflict, correct predictions are + *ensured regardless of how the termination condition is computed by this + *method. Due to the substantially higher cost of LL prediction, the + *prediction should only fall back to LL when the additional lookahead + *cannot lead to a unique SLL prediction.

            + * + *

            Assuming combined SLL+LL parsing, an SLL configuration set with only + *conflicting subsets should fall back to full LL, even if the + *configuration sets don't resolve to the same alternative (e.g. + *{@code {1,2}} and {@code {3,4}}. If there is at least one non-conflicting + *configuration, SLL could continue with the hopes that more lookahead will + *resolve via one of those non-conflicting configurations.

            + * + *

            Here's the prediction termination rule them: SLL (for SLL+LL parsing) + *stops when it sees only conflicting configuration subsets. In contrast, + *full LL keeps going when there is uncertainty.

            + * + *

            HEURISTIC

            + * + *

            As a heuristic, we stop prediction when we see any conflicting subset + *unless we see a state that only has one alternative associated with it. + *The single-alt-state thing lets prediction continue upon rules like + *(otherwise, it would admit defeat too soon):

            + * + *

            {@code [12|1|[], 6|2|[], 12|2|[]]. s : (ID | ID ID?) ';' ;}

            + * + *

            When the ATN simulation reaches the state before {@code ';'}, it has a + *DFA state that looks like: {@code [12|1|[], 6|2|[], 12|2|[]]}. Naturally + *{@code 12|1|[]} and {@code 12|2|[]} conflict, but we cannot stop + *processing this node because alternative to has another way to continue, + *via {@code [6|2|[]]}.

            + * + *

            It also let's us continue for this rule:

            + * + *

            {@code [1|1|[], 1|2|[], 8|3|[]] a : A | A | A B ;}

            + * + *

            After matching input A, we reach the stop state for rule A, state 1. + *State 8 is the state right before B. Clearly alternatives 1 and 2 + *conflict and no amount of further lookahead will separate the two. + *However, alternative 3 will be able to continue and so we do not stop + *working on this state. In the previous example, we're concerned with + *states associated with the conflicting alternatives. Here alt 3 is not + *associated with the conflicting configs, but since we can continue + *looking for input reasonably, don't declare the state done.

            + * + *

            PURE SLL PARSING

            + * + *

            To handle pure SLL parsing, all we have to do is make sure that we + *combine stack contexts for configurations that differ only by semantic + *predicate. From there, we can do the usual SLL termination heuristic.

            + * + *

            PREDICATES IN SLL+LL PARSING

            + * + *

            SLL decisions don't evaluate predicates until after they reach DFA stop + *states because they need to create the DFA cache that works in all + *semantic situations. In contrast, full LL evaluates predicates collected + *during start state computation so it can ignore predicates thereafter. + *This means that SLL termination detection can totally ignore semantic + *predicates.

            + * + *

            Implementation-wise, {@link ATNConfigSet} combines stack contexts but not + *semantic predicate contexts so we might see two configurations like the + *following.

            + * + *

            {@code (s, 1, x, {}), (s, 1, x', {p})}

            + * + *

            Before testing these configurations against others, we have to merge + *{@code x} and {@code x'} (without modifying the existing configurations). + *For example, we test {@code (x+x')==x''} when looking for conflicts in + *the following configurations.

            + * + *

            {@code (s, 1, x, {}), (s, 1, x', {p}), (s, 2, x'', {})}

            + * + *

            If the configuration set has predicates (as indicated by + *{@link ATNConfigSet//hasSemanticContext}), this algorithm makes a copy of + *the configurations to strip out all of the predicates so that a standard + *{@link ATNConfigSet} will merge everything ignoring predicates.

            + */ + public static hasSLLConflictTerminatingPrediction(mode: number, configs: ATNConfigSet): boolean { + // Configs in rule stop states indicate reaching the end of the decision + // rule (local context) or end of start rule (full context). If all + // configs meet this condition, then none of the configurations is able + // to match additional input so we terminate prediction. + // + if (PredictionMode.allConfigsInRuleStopStates(configs)) { + return true; + } + // pure SLL mode parsing + if (mode === PredictionMode.SLL) { + // Don't bother with combining configs from different semantic + // contexts if we can fail over to full LL; costs more time + // since we'll often fail over anyway. + if (configs.hasSemanticContext) { + // dup configs, tossing out semantic predicates + const dup = new ATNConfigSet(); + for (let c of configs.items) { + c = new ATNConfig({ semanticContext: SemanticContext.NONE }, c); + dup.add(c); + } + configs = dup; + } + // now we have combined contexts for configs with dissimilar preds + } + // pure SLL or combined SLL+LL mode parsing + const altSets = PredictionMode.getConflictingAltSubsets(configs); + + return PredictionMode.hasConflictingAltSet(altSets) && !PredictionMode.hasStateAssociatedWithOneAlt(configs); + }; + + /** + * Checks if any configuration in {@code configs} is in a + * {@link RuleStopState}. Configurations meeting this condition have reached + * the end of the decision rule (local context) or end of start rule (full + * context). + * + * @param configs the configuration set to test + * @returns `true` if any configuration in {@code configs} is in a + * {@link RuleStopState}, otherwise {@code false} + */ + public static hasConfigInRuleStopState(configs: ATNConfigSet): boolean { + for (const c of configs.items) { + if (c.state instanceof RuleStopState) { + return true; + } + } + + return false; + }; + + /** + * Checks if all configurations in {@code configs} are in a + * {@link RuleStopState}. Configurations meeting this condition have reached + * the end of the decision rule (local context) or end of start rule (full + * context). + * + * @param configs the configuration set to test + * @returns `true` if all configurations in {@code configs} are in a + * {@link RuleStopState}, otherwise {@code false} + */ + public static allConfigsInRuleStopStates(configs: ATNConfigSet): boolean { + for (const c of configs.items) { + if (!(c.state instanceof RuleStopState)) { + return false; + } + } + + return true; + }; + + /** + * + *Full LL prediction termination. + * + *

            Can we stop looking ahead during ATN simulation or is there some + *uncertainty as to which alternative we will ultimately pick, after + *consuming more input? Even if there are partial conflicts, we might know + *that everything is going to resolve to the same minimum alternative. That + *means we can stop since no more lookahead will change that fact. On the + *other hand, there might be multiple conflicts that resolve to different + *minimums. That means we need more look ahead to decide which of those + *alternatives we should predict.

            + * + *

            The basic idea is to split the set of configurations {@code C}, into + *conflicting subsets {@code (s, _, ctx, _)} and singleton subsets with + *non-conflicting configurations. Two configurations conflict if they have + *identical {@link ATNConfig//state} and {@link ATNConfig//context} values + *but different {@link ATNConfig//alt} value, e.g. {@code (s, i, ctx, _)} + *and {@code (s, j, ctx, _)} for {@code i!=j}.

            + * + *

            Reduce these configuration subsets to the set of possible alternatives. + *You can compute the alternative subsets in one pass as follows:

            + * + *

            {@code A_s,ctx = {i | (s, i, ctx, _)}} for each configuration in + *{@code C} holding {@code s} and {@code ctx} fixed.

            + * + *

            Or in pseudo-code, for each configuration {@code c} in {@code C}:

            + * + *
            +     *map[c] U= c.{@link ATNConfig//alt alt} // map hash/equals uses s and x, not
            +     *alt and not pred
            +     *
            + * + *

            The values in {@code map} are the set of {@code A_s,ctx} sets.

            + * + *

            If {@code |A_s,ctx|=1} then there is no conflict associated with + *{@code s} and {@code ctx}.

            + * + *

            Reduce the subsets to singletons by choosing a minimum of each subset. If + *the union of these alternative subsets is a singleton, then no amount of + *more lookahead will help us. We will always pick that alternative. If, + *however, there is more than one alternative, then we are uncertain which + *alternative to predict and must continue looking for resolution. We may + *or may not discover an ambiguity in the future, even if there are no + *conflicting subsets this round.

            + * + *

            The biggest sin is to terminate early because it means we've made a + *decision but were uncertain as to the eventual outcome. We haven't used + *enough lookahead. On the other hand, announcing a conflict too late is no + *big deal; you will still have the conflict. It's just inefficient. It + *might even look until the end of file.

            + * + *

            No special consideration for semantic predicates is required because + *predicates are evaluated on-the-fly for full LL prediction, ensuring that + *no configuration contains a semantic context during the termination + *check.

            + * + *

            CONFLICTING CONFIGS

            + * + *

            Two configurations {@code (s, i, x)} and {@code (s, j, x')}, conflict + *when {@code i!=j} but {@code x=x'}. Because we merge all + *{@code (s, i, _)} configurations together, that means that there are at + *most {@code n} configurations associated with state {@code s} for + *{@code n} possible alternatives in the decision. The merged stacks + *complicate the comparison of configuration contexts {@code x} and + *{@code x'}. Sam checks to see if one is a subset of the other by calling + *merge and checking to see if the merged result is either {@code x} or + *{@code x'}. If the {@code x} associated with lowest alternative {@code i} + *is the superset, then {@code i} is the only possible prediction since the + *others resolve to {@code min(i)} as well. However, if {@code x} is + *associated with {@code j>i} then at least one stack configuration for + *{@code j} is not in conflict with alternative {@code i}. The algorithm + *should keep going, looking for more lookahead due to the uncertainty.

            + * + *

            For simplicity, I'm doing a equality check between {@code x} and + *{@code x'} that lets the algorithm continue to consume lookahead longer + *than necessary. The reason I like the equality is of course the + *simplicity but also because that is the test you need to detect the + *alternatives that are actually in conflict.

            + * + *

            CONTINUE/STOP RULE

            + * + *

            Continue if union of resolved alternative sets from non-conflicting and + *conflicting alternative subsets has more than one alternative. We are + *uncertain about which alternative to predict.

            + * + *

            The complete set of alternatives, {@code [i for (_,i,_)]}, tells us which + *alternatives are still in the running for the amount of input we've + *consumed at this point. The conflicting sets let us to strip away + *configurations that won't lead to more states because we resolve + *conflicts to the configuration with a minimum alternate for the + *conflicting set.

            + * + *

            CASES

            + * + *
              + * + *
            • no conflicts and more than 1 alternative in set => continue
            • + * + *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s, 3, z)}, + *{@code (s', 1, y)}, {@code (s', 2, y)} yields non-conflicting set + *{@code {3}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} = + *{@code {1,3}} => continue + *
            • + * + *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)}, + *{@code (s', 2, y)}, {@code (s'', 1, z)} yields non-conflicting set + *{@code {1}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} = + *{@code {1}} => stop and predict 1
            • + * + *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)}, + *{@code (s', 2, y)} yields conflicting, reduced sets {@code {1}} U + *{@code {1}} = {@code {1}} => stop and predict 1, can announce + *ambiguity {@code {1,2}}
            • + * + *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 2, y)}, + *{@code (s', 3, y)} yields conflicting, reduced sets {@code {1}} U + *{@code {2}} = {@code {1,2}} => continue
            • + * + *
            • {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 3, y)}, + *{@code (s', 4, y)} yields conflicting, reduced sets {@code {1}} U + *{@code {3}} = {@code {1,3}} => continue
            • + * + *
            + * + *

            EXACT AMBIGUITY DETECTION

            + * + *

            If all states report the same conflicting set of alternatives, then we + *know we have the exact ambiguity set.

            + * + *

            |A_i|>1 and + *A_i = A_j for all i, j.

            + * + *

            In other words, we continue examining lookahead until all {@code A_i} + *have more than one alternative and all {@code A_i} are the same. If + *{@code A={{1,2}, {1,3}}}, then regular LL prediction would terminate + *because the resolved set is {@code {1}}. To determine what the real + *ambiguity is, we have to know whether the ambiguity is between one and + *two or one and three so we keep going. We can only stop prediction when + *we need exact ambiguity detection when the sets look like + *{@code A={{1,2}}} or {@code {{1,2},{1,2}}}, etc...

            + */ + public static resolvesToJustOneViableAlt(altSets: BitSet[]): number { + return PredictionMode.getSingleViableAlt(altSets); + }; + + /** + * Determines if every alternative subset in {@code altSets} contains more + * than one alternative. + * + * @param altSets a collection of alternative subsets + * @returns `true` if every {@link BitSet} in {@code altSets} has + * {@link BitSet//cardinality cardinality} > 1, otherwise {@code false} + */ + public static allSubsetsConflict(altSets: BitSet[]): boolean { + return !PredictionMode.hasNonConflictingAltSet(altSets); + }; + + /** + * Determines if any single alternative subset in {@code altSets} contains + * exactly one alternative. + * + * @param altSets a collection of alternative subsets + * @returns `true` if {@code altSets} contains a {@link BitSet} with + * {@link BitSet//cardinality cardinality} 1, otherwise {@code false} + */ + public static hasNonConflictingAltSet(altSets: BitSet[]): boolean { + for (const alts of altSets) { + if (alts.length === 1) { + return true; + } + } + + return false; + }; + + /** + * Determines if any single alternative subset in {@code altSets} contains + * more than one alternative. + * + * @param altSets a collection of alternative subsets + * @returns `true` if {@code altSets} contains a {@link BitSet} with + * {@link BitSet//cardinality cardinality} > 1, otherwise {@code false} + */ + public static hasConflictingAltSet(altSets: BitSet[]): boolean { + for (const alts of altSets) { + if (alts.length > 1) { + return true; + } + } + + return false; + }; + + /** + * Determines if every alternative subset in {@code altSets} is equivalent. + * + * @param altSets a collection of alternative subsets + * @returns `true` if every member of {@code altSets} is equal to the + * others, otherwise {@code false} + */ + public static allSubsetsEqual(altSets: BitSet[]): boolean { + let first = null; + for (const alts of altSets) { + if (first === null) { + first = alts; + } else if (alts !== first) { + return false; + } + } + + return true; + }; + + /** + * Returns the unique alternative predicted by all alternative subsets in + * {@code altSets}. If no such alternative exists, this method returns + * {@link ATN//INVALID_ALT_NUMBER}. + * + * @param altSets a collection of alternative subsets + */ + public static getUniqueAlt(altSets: BitSet[]): number { + const all = PredictionMode.getAlts(altSets); + if (all.length === 1) { + return all.nextSetBit(0)!; + } else { + return ATN.INVALID_ALT_NUMBER; + } + }; + + /** + * Gets the complete set of represented alternatives for a collection of + * alternative subsets. This method returns the union of each {@link BitSet} + * in {@code altSets}. + * + * @param altSets a collection of alternative subsets + * @returns the set of represented alternatives in {@code altSets} + */ + public static getAlts(altSets: BitSet[]): BitSet { + const all = new BitSet(); + altSets.forEach((alts) => { + all.or(alts); + }); + + return all; + }; + + /** + * This function gets the conflicting alt subsets from a configuration set. + * For each configuration {@code c} in {@code configs}: + * + *
            +     * map[c] U= c.{@link ATNConfig//alt alt} // map hash/equals uses s and x, not
            +     * alt and not pred
            +     * 
            + */ + public static getConflictingAltSubsets(configs: ATNConfigSet): BitSet[] { + const configToAlts = new HashMap( + (cfg: ATNConfig) => { return HashCode.hashStuff(cfg.state.stateNumber, cfg.context); }, + (c1: ATNConfig, c2: ATNConfig) => { + return c1.state.stateNumber === c2.state.stateNumber && (c1.context?.equals(c2.context) ?? true); + }, + ); + + configs.items.forEach((cfg) => { + let alts = configToAlts.get(cfg); + if (alts === null) { + alts = new BitSet(); + configToAlts.set(cfg, alts); + } + alts.set(cfg.alt); + }); + + return configToAlts.getValues(); + }; + + /** + * Get a map from state to alt subset from a configuration set. For each + * configuration {@code c} in {@code configs}: + * + *
            +     * map[c.{@link ATNConfig//state state}] U= c.{@link ATNConfig//alt alt}
            +     * 
            + */ + public static getStateToAltMap(configs: ATNConfigSet): HashMap { + const m = new HashMap(); + configs.items.forEach((c) => { + let alts = m.get(c.state); + if (!alts) { + alts = new BitSet(); + m.set(c.state, alts); + } + alts.set(c.alt); + }); + + return m; + }; + + 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) => { + const stateNumber = c.state.stateNumber; + if (!counts[stateNumber]) { + counts[stateNumber] = 0; + } + counts[stateNumber]++; + }); + + return Object.values(counts).some((count) => { return count === 1; }); + }; + + public static getSingleViableAlt(altSets: BitSet[]): number { + let result = null; + for (const alts of altSets) { + const minAlt = alts.nextSetBit(0); + if (result === null) { + result = minAlt; + } else if (result !== minAlt) { // more than 1 viable alt + return ATN.INVALID_ALT_NUMBER; + } + } + + return result ?? 0; + }; +}; diff --git a/src/atn/RangeTransition.d.ts b/src/atn/RangeTransition.d.ts deleted file mode 100644 index c54a04b..0000000 --- a/src/atn/RangeTransition.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class RangeTransition extends Transition { - public start: number; - public stop: number; - - public constructor(target: ATNState, start: number, stop: number); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/RangeTransition.js b/src/atn/RangeTransition.js deleted file mode 100644 index 7cacd4e..0000000 --- a/src/atn/RangeTransition.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntervalSet } from "../misc/IntervalSet.js"; -import { Transition } from "./Transition.js"; -import { TransitionType } from './TransitionType.js'; - -export class RangeTransition extends Transition { - constructor(target, start, stop) { - super(target); - this.serializationType = TransitionType.RANGE; - this.start = start; - this.stop = stop; - this.label = this.makeLabel(); - } - - makeLabel() { - const s = new IntervalSet(); - s.addRange(this.start, this.stop); - return s; - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return symbol >= this.start && symbol <= this.stop; - } - - toString() { - return "'" + String.fromCharCode(this.start) + "'..'" + String.fromCharCode(this.stop) + "'"; - } -} diff --git a/src/atn/RangeTransition.ts b/src/atn/RangeTransition.ts new file mode 100644 index 0000000..37a4b83 --- /dev/null +++ b/src/atn/RangeTransition.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { IntervalSet } from "../misc/IntervalSet.js"; +import { ATNState } from "./ATNState.js"; +import { Transition } from "./Transition.js"; +import { TransitionType } from "./TransitionType.js"; + +export class RangeTransition extends Transition { + public readonly start: number; + public readonly stop: number; + + readonly #label = new IntervalSet(); + + public constructor(target: ATNState, start: number, stop: number) { + super(target); + this.start = start; + this.stop = stop; + this.#label.addRange(start, stop); + } + + public override get label(): IntervalSet { + return this.#label; + } + + public get serializationType(): number { + return TransitionType.RANGE; + } + + public matches(symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return symbol >= this.start && symbol <= this.stop; + } + + public override toString(): string { + return "'" + String.fromCharCode(this.start) + "'..'" + String.fromCharCode(this.stop) + "'"; + } +} diff --git a/src/atn/RuleContext.d.ts b/src/atn/RuleContext.d.ts deleted file mode 100644 index d3ba7da..0000000 --- a/src/atn/RuleContext.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../Parser.js"; -import { ParseTree } from "../tree/ParseTree.js"; -import { Interval } from "../misc/Interval.js"; -import { ParseTreeVisitor } from "../tree/ParseTreeVisitor.js"; - -export declare class RuleContext implements ParseTree { - public get parent(): RuleContext | null; - public children: ParseTree[] | null; - public invokingState: number; - - public getParent(): ParseTree | null; - public depth(): number; - - /** - * A context is empty if there is no invoking state; meaning nobody call - * current context. - */ - public isEmpty(): boolean; - - public getSourceInterval(): Interval; - - public get ruleContext(): RuleContext; - - public get ruleIndex(): number; - - public getPayload(): RuleContext; - - /** - * Return the combined text of all child nodes. This method only considers - * tokens which have been added to the parse tree. - *

            - * Since tokens on hidden channels (e.g. whitespace or comments) are not - * added to the parse trees, they will not appear in the output of this - * method. - */ - public getText(): string; - - /** - * For rule associated with this parse tree internal node, return - * the outer alternative number used to match the input. Default - * implementation does not compute nor store this alt num. Create - * a subclass of ParserRuleContext with backing field and set - * option contextSuperClass. - * to set it. - */ - public getAltNumber(): number; - - /** - * Set the outer alternative number for this context node. Default - * implementation does nothing to avoid backing field overhead for - * trees that don't need it. Create - * a subclass of ParserRuleContext with backing field and set - * option contextSuperClass. - */ - public setAltNumber(altNumber: number): void; - - public getChild(i: number): RuleContext | null; - - public getChildCount(): number; - - public accept(visitor: ParseTreeVisitor): Result; - - /** - * Print out a whole tree, not just a node, in LISP format - * (root child1 .. childN). Print just a node if this is a leaf. - */ - public toStringTree(): string; - public toStringTree(ruleNames: string[] | null, recog: Parser): string; -} diff --git a/src/atn/RuleStartState.d.ts b/src/atn/RuleStartState.d.ts deleted file mode 100644 index 280a93d..0000000 --- a/src/atn/RuleStartState.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { RuleStopState } from "./RuleStopState.js"; - -export declare class RuleStartState extends ATNState { - public stopState: RuleStopState; - public isLeftRecursiveRule: boolean; - public isPrecedenceRule: boolean; -} diff --git a/src/atn/RuleStartState.js b/src/atn/RuleStartState.ts similarity index 64% rename from src/atn/RuleStartState.js rename to src/atn/RuleStartState.ts index e153f33..d123b56 100644 --- a/src/atn/RuleStartState.js +++ b/src/atn/RuleStartState.ts @@ -6,14 +6,19 @@ import { ATNState } from "./ATNState.js"; import { ATNStateType } from "./ATNStateType.js"; +import { RuleStopState } from "./RuleStopState.js"; export class RuleStartState extends ATNState { - constructor() { + public stopState: RuleStopState; + public isLeftRecursiveRule: boolean; + public isPrecedenceRule: boolean; + + public constructor() { super(); this.isPrecedenceRule = false; } - get stateType() { + public override get stateType(): number { return ATNStateType.RULE_START; } diff --git a/src/atn/RuleStopState.d.ts b/src/atn/RuleStopState.d.ts deleted file mode 100644 index e2eeec2..0000000 --- a/src/atn/RuleStopState.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; - -export declare class RuleStopState extends ATNState { -} diff --git a/src/atn/RuleStopState.js b/src/atn/RuleStopState.ts similarity index 93% rename from src/atn/RuleStopState.js rename to src/atn/RuleStopState.ts index 2ddcfa4..1f8c221 100644 --- a/src/atn/RuleStopState.js +++ b/src/atn/RuleStopState.ts @@ -14,7 +14,7 @@ import { ATNStateType } from "./ATNStateType.js"; * error handling */ export class RuleStopState extends ATNState { - get stateType() { + public override get stateType(): number { return ATNStateType.RULE_STOP; } diff --git a/src/atn/RuleTransition.d.ts b/src/atn/RuleTransition.d.ts deleted file mode 100644 index ed4df6a..0000000 --- a/src/atn/RuleTransition.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class RuleTransition extends Transition { - public ruleIndex: number; - public precedence: number; - public followState: ATNState; - public isEpsilon: boolean; - - public constructor(ruleStart: ATNState, ruleIndex: number, precedence: number, followState: ATNState); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; -} diff --git a/src/atn/RuleTransition.js b/src/atn/RuleTransition.ts similarity index 55% rename from src/atn/RuleTransition.js rename to src/atn/RuleTransition.ts index 47add22..aa5c538 100644 --- a/src/atn/RuleTransition.js +++ b/src/atn/RuleTransition.ts @@ -4,22 +4,33 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ATNState } from "./ATNState.js"; import { Transition } from "./Transition.js"; import { TransitionType } from "./TransitionType.js"; export class RuleTransition extends Transition { - constructor(ruleStart, ruleIndex, precedence, followState) { + public ruleIndex: number; + public precedence: number; + public followState: ATNState; + + public constructor(ruleStart: ATNState, ruleIndex: number, precedence: number, followState: ATNState) { super(ruleStart); // ptr to the rule definition object for this rule ref this.ruleIndex = ruleIndex; this.precedence = precedence; // what node to begin computations following ref to rule this.followState = followState; - this.serializationType = TransitionType.RULE; - this.isEpsilon = true; } - matches(symbol, minVocabSymbol, maxVocabSymbol) { + public override get isEpsilon(): boolean { + return true; + } + + public override get serializationType(): number { + return TransitionType.RULE; + } + + public override matches(_symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { return false; } } diff --git a/src/atn/SemanticContext.d.ts b/src/atn/SemanticContext.d.ts deleted file mode 100644 index 96164e4..0000000 --- a/src/atn/SemanticContext.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../Parser.js"; -import { RuleContext } from "./RuleContext.js"; - -export declare class SemanticContext { - public hashCode(): number; - public evaluate(parser: Parser, outerContext: RuleContext): boolean; -} diff --git a/src/atn/SemanticContext.js b/src/atn/SemanticContext.js deleted file mode 100644 index 96217e2..0000000 --- a/src/atn/SemanticContext.js +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { equalArrays } from "../utils/equalArrays.js"; -import { HashCode } from "../misc/HashCode.js"; -import { HashSet } from "../misc/HashSet.js"; - -/** - * A tree structure used to record the semantic context in which - * an ATN configuration is valid. It's either a single predicate, - * a conjunction {@code p1&&p2}, or a sum of products {@code p1||p2}. - * - *

            I have scoped the {@link AND}, {@link OR}, and {@link Predicate} subclasses of - * {@link SemanticContext} within the scope of this outer class.

            - */ -export class SemanticContext { - - hashCode() { - const hash = new HashCode(); - this.updateHashCode(hash); - return hash.finish(); - } - - /** - * For context independent predicates, we evaluate them without a local - * context (i.e., null context). That way, we can evaluate them without - * having to create proper rule-specific context during prediction (as - * opposed to the parser, which creates them naturally). In a practical - * sense, this avoids a cast exception from RuleContext to myruleContext. - * - *

            For context dependent predicates, we must pass in a local context so that - * references such as $arg evaluate properly as _localctx.arg. We only - * capture context dependent predicates in the context in which we begin - * prediction, so we passed in the outer context here in case of context - * dependent predicate evaluation.

            - */ - evaluate(parser, outerContext) { } - - /** - * Evaluate the precedence predicates for the context and reduce the result. - * - * @param parser The parser instance. - * @param outerContext The current parser context object. - * @return The simplified semantic context after precedence predicates are - * evaluated, which will be one of the following values. - *
              - *
            • {@link //NONE}: if the predicate simplifies to {@code true} after - * precedence predicates are evaluated.
            • - *
            • {@code null}: if the predicate simplifies to {@code false} after - * precedence predicates are evaluated.
            • - *
            • {@code this}: if the semantic context is not changed as a result of - * precedence predicate evaluation.
            • - *
            • A non-{@code null} {@link SemanticContext}: the new simplified - * semantic context after precedence predicates are evaluated.
            • - *
            - */ - evalPrecedence(parser, outerContext) { - return this; - } - - static andContext(a, b) { - if (a === null || a === SemanticContext.NONE) { - return b; - } - if (b === null || b === SemanticContext.NONE) { - return a; - } - const result = new AND(a, b); - if (result.opnds.length === 1) { - return result.opnds[0]; - } else { - return result; - } - } - - static orContext(a, b) { - if (a === null) { - return b; - } - if (b === null) { - return a; - } - if (a === SemanticContext.NONE || b === SemanticContext.NONE) { - return SemanticContext.NONE; - } - const result = new OR(a, b); - if (result.opnds.length === 1) { - return result.opnds[0]; - } else { - return result; - } - } -} - -class AND extends SemanticContext { - /** - * A semantic context which is true whenever none of the contained contexts - * is false - */ - constructor(a, b) { - super(); - const operands = new HashSet(); - if (a instanceof AND) { - a.opnds.map(function (o) { - operands.add(o); - }); - } else { - operands.add(a); - } - if (b instanceof AND) { - b.opnds.map(function (o) { - operands.add(o); - }); - } else { - operands.add(b); - } - const precedencePredicates = filterPrecedencePredicates(operands); - if (precedencePredicates.length > 0) { - // interested in the transition with the lowest precedence - let reduced = null; - precedencePredicates.map(function (p) { - if (reduced === null || p.precedence < reduced.precedence) { - reduced = p; - } - }); - operands.add(reduced); - } - this.opnds = Array.from(operands.values()); - } - - equals(other) { - if (this === other) { - return true; - } else if (!(other instanceof AND)) { - return false; - } else { - return equalArrays(this.opnds, other.opnds); - } - } - - updateHashCode(hash) { - hash.update(this.opnds, "AND"); - } - - /** - * {@inheritDoc} - * - *

            - * The evaluation of predicates by this context is short-circuiting, but - * unordered.

            - */ - evaluate(parser, outerContext) { - for (let i = 0; i < this.opnds.length; i++) { - if (!this.opnds[i].evaluate(parser, outerContext)) { - return false; - } - } - return true; - } - - evalPrecedence(parser, outerContext) { - let differs = false; - const operands = []; - for (let i = 0; i < this.opnds.length; i++) { - const context = this.opnds[i]; - const evaluated = context.evalPrecedence(parser, outerContext); - differs |= (evaluated !== context); - if (evaluated === null) { - // The AND context is false if any element is false - return null; - } else if (evaluated !== SemanticContext.NONE) { - // Reduce the result by skipping true elements - operands.push(evaluated); - } - } - if (!differs) { - return this; - } - if (operands.length === 0) { - // all elements were true, so the AND context is true - return SemanticContext.NONE; - } - let result = null; - operands.map(function (o) { - result = result === null ? o : SemanticContext.andContext(result, o); - }); - return result; - } - - toString() { - const s = this.opnds.map(o => o.toString()); - return (s.length > 3 ? s.slice(3) : s).join("&&"); - } -} - -class OR extends SemanticContext { - /** - * A semantic context which is true whenever at least one of the contained - * contexts is true - */ - constructor(a, b) { - super(); - const operands = new HashSet(); - if (a instanceof OR) { - a.opnds.map(function (o) { - operands.add(o); - }); - } else { - operands.add(a); - } - if (b instanceof OR) { - b.opnds.map(function (o) { - operands.add(o); - }); - } else { - operands.add(b); - } - - const precedencePredicates = filterPrecedencePredicates(operands); - if (precedencePredicates.length > 0) { - // interested in the transition with the highest precedence - const s = precedencePredicates.sort(function (a, b) { - return a.compareTo(b); - }); - const reduced = s[s.length - 1]; - operands.add(reduced); - } - this.opnds = Array.from(operands.values()); - } - - equals(other) { - if (this === other) { - return true; - } else if (!(other instanceof OR)) { - return false; - } else { - return equalArrays(this.opnds, other.opnds); - } - } - - updateHashCode(hash) { - hash.update(this.opnds, "OR"); - } - - /** - *

            - * The evaluation of predicates by this context is short-circuiting, but - * unordered.

            - */ - evaluate(parser, outerContext) { - for (let i = 0; i < this.opnds.length; i++) { - if (this.opnds[i].evaluate(parser, outerContext)) { - return true; - } - } - return false; - } - - evalPrecedence(parser, outerContext) { - let differs = false; - const operands = []; - for (let i = 0; i < this.opnds.length; i++) { - const context = this.opnds[i]; - const evaluated = context.evalPrecedence(parser, outerContext); - differs |= (evaluated !== context); - if (evaluated === SemanticContext.NONE) { - // The OR context is true if any element is true - return SemanticContext.NONE; - } else if (evaluated !== null) { - // Reduce the result by skipping false elements - operands.push(evaluated); - } - } - if (!differs) { - return this; - } - if (operands.length === 0) { - // all elements were false, so the OR context is false - return null; - } - const result = null; - operands.map(function (o) { - return result === null ? o : SemanticContext.orContext(result, o); - }); - return result; - } - - toString() { - const s = this.opnds.map(o => o.toString()); - return (s.length > 3 ? s.slice(3) : s).join("||"); - } -} - -function filterPrecedencePredicates(set) { - const result = []; - set.values().map(function (context) { - if (context instanceof SemanticContext.PrecedencePredicate) { - result.push(context); - } - }); - return result; -} diff --git a/src/atn/SemanticContext.ts b/src/atn/SemanticContext.ts new file mode 100644 index 0000000..eaf9fc0 --- /dev/null +++ b/src/atn/SemanticContext.ts @@ -0,0 +1,426 @@ +/* +* Copyright (c) The ANTLR Project. All rights reserved. +* Use of this file is governed by the BSD 3-clause license that +* can be found in the LICENSE.txt file in the project root. +*/ + +/* eslint-disable max-classes-per-file, jsdoc/require-jsdoc, jsdoc/require-param, jsdoc/require-returns */ + +import { Recognizer } from "../Recognizer.js"; +import { RuleContext } from "../RuleContext.js"; +import { HashCode } from "../misc/HashCode.js"; +import { HashSet } from "../misc/HashSet.js"; + +import { IComparable, equalArrays } from "../utils/helpers.js"; +import { ATNSimulator } from "./ATNSimulator.js"; + +/** + * A tree structure used to record the semantic context in which + * an ATN configuration is valid. It's either a single predicate, + * a conjunction {@code p1&&p2}, or a sum of products {@code p1||p2}. + * + *

            I have scoped the {@link AND}, {@link OR}, and {@link SemanticContext.Predicate} subclasses of + * {@link SemanticContext} within the scope of this outer class.

            + */ +export abstract class SemanticContext implements IComparable { + public static andContext(a: SemanticContext | null, b: SemanticContext | null): SemanticContext | null { + if (a === null || a === SemanticContext.NONE) { + return b; + } + if (b === null || b === SemanticContext.NONE) { + return a; + } + const result = new AND(a, b); + if (result.opnds.length === 1) { + return result.opnds[0]; + } else { + return result; + } + } + + public static orContext(a: SemanticContext | null, b: SemanticContext | null): SemanticContext | null { + if (a === null) { + return b; + } + if (b === null) { + return a; + } + if (a === SemanticContext.NONE || b === SemanticContext.NONE) { + return SemanticContext.NONE; + } + const result = new OR(a, b); + if (result.opnds.length === 1) { + return result.opnds[0]; + } else { + return result; + } + } + + protected static filterPrecedencePredicates(set: HashSet): SemanticContext.PrecedencePredicate[] { + const result: SemanticContext.PrecedencePredicate[] = []; + set.values().forEach((context) => { + if (context instanceof SemanticContext.PrecedencePredicate) { + result.push(context); + } + }); + + return result; + }; + + public hashCode(): number { + const hash = new HashCode(); + this.updateHashCode(hash); + + return hash.finish(); + } + + /** + * Evaluate the precedence predicates for the context and reduce the result. + * + * @param _parser The parser instance. + * @param _parserCallStack The current parser context object. + * @returns The simplified semantic context after precedence predicates are + * evaluated, which will be one of the following values. + *
              + *
            • {@link NONE}: if the predicate simplifies to {@code true} after + * precedence predicates are evaluated.
            • + *
            • {@code null}: if the predicate simplifies to {@code false} after + * precedence predicates are evaluated.
            • + *
            • {@code this}: if the semantic context is not changed as a result of + * precedence predicate evaluation.
            • + *
            • A non-{@code null} {@link SemanticContext}: the new simplified + * semantic context after precedence predicates are evaluated.
            • + *
            + */ + public evalPrecedence(_parser: Recognizer, + _parserCallStack: RuleContext | null): SemanticContext | null { + return this; + }; + + /** + * For context independent predicates, we evaluate them without a local + * context (i.e., null context). That way, we can evaluate them without + * having to create proper rule-specific context during prediction (as + * opposed to the parser, which creates them naturally). In a practical + * sense, this avoids a cast exception from RuleContext to myRuleContext. + * + *

            For context dependent predicates, we must pass in a local context so that + * references such as $arg evaluate properly as _localctx.arg. We only + * capture context dependent predicates in the context in which we begin + * prediction, so we passed in the outer context here in case of context + * dependent predicate evaluation.

            + */ + public abstract evaluate(parser: Recognizer, parserCallStack: RuleContext): boolean; + + public abstract equals(other: unknown): boolean; + public abstract updateHashCode(hash: HashCode): void; +} + +class AND extends SemanticContext { + public readonly opnds: SemanticContext[]; + + /** + * A semantic context which is true whenever none of the contained contexts + * is false + */ + public constructor(a: SemanticContext, b: SemanticContext) { + super(); + const operands = new HashSet(); + if (a instanceof AND) { + a.opnds.forEach((o) => { + operands.add(o); + }); + } else { + operands.add(a); + } + if (b instanceof AND) { + b.opnds.forEach((o) => { + operands.add(o); + }); + } else { + operands.add(b); + } + const precedencePredicates = SemanticContext.filterPrecedencePredicates(operands); + if (precedencePredicates.length > 0) { + // interested in the transition with the lowest precedence + let reduced: SemanticContext.PrecedencePredicate | null = null; + precedencePredicates.forEach((p) => { + if (reduced === null || p.precedence < reduced.precedence) { + reduced = p; + } + }); + if (reduced) { + operands.add(reduced); + } + } + this.opnds = operands.values(); + } + + public override equals(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof AND)) { + return false; + } else { + return equalArrays(this.opnds, other.opnds); + } + } + + public updateHashCode(hash: HashCode): void { + hash.update(this.opnds); + hash.updateWithHashCode(3813686060); // Hash code of "AND". + } + + /** + * {@inheritDoc} + * + *

            + * The evaluation of predicates by this context is short-circuiting, but + * unordered.

            + */ + public evaluate(parser: Recognizer, + parserCallStack: RuleContext): boolean { + for (const operand of this.opnds) { + if (!operand.evaluate(parser, parserCallStack)) { + return false; + } + } + + return true; + } + + public override evalPrecedence(parser: Recognizer, + parserCallStack: RuleContext): SemanticContext | null { + let differs = false; + const operands = []; + for (const context of this.opnds) { + const evaluated = context.evalPrecedence(parser, parserCallStack); + differs ||= (evaluated !== context); + if (evaluated === null) { + // The AND context is false if any element is false + return null; + } else if (evaluated !== SemanticContext.NONE) { + // Reduce the result by skipping true elements + operands.push(evaluated); + } + } + if (!differs) { + return this; + } + if (operands.length === 0) { + // all elements were true, so the AND context is true + return SemanticContext.NONE; + } + let result: SemanticContext | null = null; + operands.forEach((o) => { + result = result === null ? o : SemanticContext.andContext(result, o); + }); + + return result; + } + + public override toString(): string { + const s = this.opnds.map((o) => { return o.toString(); }); + + return (s.length > 3 ? s.slice(3) : s).join("&&"); + } +} + +class OR extends SemanticContext { + public readonly opnds: SemanticContext[]; + + /** + * A semantic context which is true whenever at least one of the contained + * contexts is true + */ + public constructor(a: SemanticContext, b: SemanticContext) { + super(); + const operands = new HashSet(); + if (a instanceof OR) { + a.opnds.forEach((o) => { + operands.add(o); + }); + } else { + operands.add(a); + } + if (b instanceof OR) { + b.opnds.forEach((o) => { + operands.add(o); + }); + } else { + operands.add(b); + } + + const precedencePredicates = SemanticContext.filterPrecedencePredicates(operands); + if (precedencePredicates.length > 0) { + // interested in the transition with the highest precedence + const s = precedencePredicates.sort((a, b) => { + return a.compareTo(b); + }); + const reduced = s[s.length - 1]; + operands.add(reduced); + } + this.opnds = Array.from(operands.values()); + } + + public override equals(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof OR)) { + return false; + } else { + return equalArrays(this.opnds, other.opnds); + } + } + + public updateHashCode(hash: HashCode): void { + hash.update(this.opnds); + hash.updateWithHashCode(3383313031); // Hash code of "OR". + } + + /** + *

            + * The evaluation of predicates by this context is short-circuiting, but + * unordered.

            + */ + public evaluate(parser: Recognizer, + parserCallStack: RuleContext): boolean { + for (const operand of this.opnds) { + if (operand.evaluate(parser, parserCallStack)) { + return true; + } + } + + return false; + } + + public override evalPrecedence(parser: Recognizer, + parserCallStack: RuleContext): SemanticContext | null { + let differs = false; + const operands = []; + for (const context of this.opnds) { + const evaluated = context.evalPrecedence(parser, parserCallStack); + differs ||= (evaluated !== context); + if (evaluated === SemanticContext.NONE) { + // The OR context is true if any element is true + return SemanticContext.NONE; + } else if (evaluated !== null) { + // Reduce the result by skipping false elements + operands.push(evaluated); + } + } + if (!differs) { + return this; + } + if (operands.length === 0) { + // all elements were false, so the OR context is false + return null; + } + let result: SemanticContext | null = null; + operands.forEach((o) => { + result = result === null ? o : SemanticContext.orContext(result, o); + }); + + return result; + } + + public override toString() { + const s = this.opnds.map((o) => { return o.toString(); }); + + return (s.length > 3 ? s.slice(3) : s).join("||"); + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace SemanticContext { + export class Predicate extends SemanticContext { + public readonly ruleIndex: number; + public readonly predIndex: number; + public readonly isCtxDependent: boolean; // e.g., $i ref in pred + + public constructor(ruleIndex?: number, predIndex?: number, isCtxDependent?: boolean) { + super(); + this.ruleIndex = ruleIndex ?? -1; + this.predIndex = predIndex ?? -1; + this.isCtxDependent = isCtxDependent ?? false; + } + + public override evaluate(parser: Recognizer, outerContext: RuleContext): boolean { + const localctx = this.isCtxDependent ? outerContext : null; + + return parser.sempred(localctx, this.ruleIndex, this.predIndex); + } + + public override updateHashCode(hash: HashCode): void { + hash.update(this.ruleIndex, this.predIndex, this.isCtxDependent); + } + + public override equals(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof Predicate)) { + return false; + } else { + return this.ruleIndex === other.ruleIndex && + this.predIndex === other.predIndex && + this.isCtxDependent === other.isCtxDependent; + } + } + + public override toString(): string { + return "{" + this.ruleIndex + ":" + this.predIndex + "}?"; + } + } + + export class PrecedencePredicate extends SemanticContext { + public readonly precedence: number; + + public constructor(precedence?: number) { + super(); + this.precedence = precedence ?? 0; + } + + public override evaluate(parser: Recognizer, outerContext: RuleContext): boolean { + return parser.precpred(outerContext, this.precedence); + } + + public override evalPrecedence(parser: Recognizer, + outerContext: RuleContext | null): SemanticContext | null { + if (parser.precpred(outerContext, this.precedence)) { + return SemanticContext.NONE; + } else { + return null; + } + } + + public compareTo(other: PrecedencePredicate): number { + return this.precedence - other.precedence; + } + + public override updateHashCode(hash: HashCode): void { + hash.update(this.precedence); + } + + public override equals(other: unknown): boolean { + if (this === other) { + return true; + } else if (!(other instanceof PrecedencePredicate)) { + return false; + } else { + return this.precedence === other.precedence; + } + } + + public override toString(): string { + return "{" + this.precedence + ">=prec}?"; + } + + } + + /** + * The default {@link SemanticContext}, which is semantically equivalent to + * a predicate of the form {@code {true}?} + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + export const NONE = new Predicate(); +} diff --git a/src/atn/SetTransition.d.ts b/src/atn/SetTransition.d.ts deleted file mode 100644 index 3d925cb..0000000 --- a/src/atn/SetTransition.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntervalSet } from "../misc/IntervalSet.js"; -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class SetTransition extends Transition { - public set: IntervalSet; - - public constructor(target: ATNState, set: IntervalSet); - - public override matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/SetTransition.js b/src/atn/SetTransition.js deleted file mode 100644 index 5e6f5fd..0000000 --- a/src/atn/SetTransition.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -// A transition containing a set of values. -import { IntervalSet } from "../misc/IntervalSet.js"; -import { Token } from '../Token.js'; -import { Transition } from "./Transition.js"; -import { TransitionType } from "./TransitionType.js"; - -export class SetTransition extends Transition { - constructor(target, set) { - super(target); - this.serializationType = TransitionType.SET; - if (set !== undefined && set !== null) { - this.label = set; - } else { - this.label = new IntervalSet(); - this.label.addOne(Token.INVALID_TYPE); - } - } - - matches(symbol, minVocabSymbol, maxVocabSymbol) { - return this.label.contains(symbol); - } - - toString() { - return this.label.toString(); - } -} diff --git a/src/atn/SetTransition.ts b/src/atn/SetTransition.ts new file mode 100644 index 0000000..9cc98b8 --- /dev/null +++ b/src/atn/SetTransition.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +// A transition containing a set of values. +import { IntervalSet } from "../misc/IntervalSet.js"; +import { Token } from "../Token.js"; +import { ATNState } from "./ATNState.js"; +import { Transition } from "./Transition.js"; +import { TransitionType } from "./TransitionType.js"; + +export class SetTransition extends Transition { + #label; + + public constructor(target: ATNState, set: IntervalSet) { + super(target); + if (set !== undefined && set !== null) { + this.#label = set; + } else { + this.#label = new IntervalSet(); + this.#label.addOne(Token.INVALID_TYPE); + } + } + + public override get label(): IntervalSet { + return this.#label; + } + + public get serializationType(): number { + return TransitionType.SET; + } + + public override matches(symbol: number, _minVocabSymbol: number, _maxVocabSymbol: number): boolean { + return this.label.contains(symbol); + } + + public override toString(): string { + return this.#label.toString(); + } +} diff --git a/src/atn/SingletonPredictionContext.js b/src/atn/SingletonPredictionContext.ts similarity index 66% rename from src/atn/SingletonPredictionContext.js rename to src/atn/SingletonPredictionContext.ts index 764a301..dbcc022 100644 --- a/src/atn/SingletonPredictionContext.js +++ b/src/atn/SingletonPredictionContext.ts @@ -4,12 +4,14 @@ * can be found in the LICENSE.txt file in the project root. */ -import { PredictionContext } from './PredictionContext.js'; +import { PredictionContext } from "./PredictionContext.js"; import { HashCode } from "../misc/HashCode.js"; export class SingletonPredictionContext extends PredictionContext { + public readonly parent: PredictionContext | null; + public readonly returnState: number; - constructor(parent, returnState) { + public constructor(parent: PredictionContext | null, returnState: number) { let hashCode = 0; const hash = new HashCode(); if (parent !== null) { @@ -23,15 +25,24 @@ export class SingletonPredictionContext extends PredictionContext { this.returnState = returnState; } - getParent(index) { + public static create(parent: PredictionContext | null, returnState: number): SingletonPredictionContext { + if (returnState === PredictionContext.EMPTY_RETURN_STATE && parent === null) { + // someone can pass in the bits of an array ctx that mean $ + return PredictionContext.EMPTY as SingletonPredictionContext; + } else { + return new SingletonPredictionContext(parent, returnState); + } + } + + public getParent(_index: number): PredictionContext | null { return this.parent; } - getReturnState(index) { + public getReturnState(_index: number): number { return this.returnState; } - equals(other) { + public equals(other: unknown): boolean { if (this === other) { return true; } else if (!(other instanceof SingletonPredictionContext)) { @@ -39,16 +50,13 @@ export class SingletonPredictionContext extends PredictionContext { } else if (this.hashCode() !== other.hashCode()) { return false; // can't be same if hash is different } else { - if (this.returnState !== other.returnState) - return false; - else if (this.parent == null) - return other.parent == null; - else - return this.parent.equals(other.parent); + if (this.returnState !== other.returnState) { return false; } + else if (this.parent == null) { return other.parent == null; } + else { return this.parent.equals(other.parent); } } } - toString() { + public override toString(): string { const up = this.parent === null ? "" : this.parent.toString(); if (up.length === 0) { if (this.returnState === PredictionContext.EMPTY_RETURN_STATE) { @@ -61,16 +69,8 @@ export class SingletonPredictionContext extends PredictionContext { } } - get length() { + public get length(): number { return 1; } - static create(parent, returnState) { - if (returnState === PredictionContext.EMPTY_RETURN_STATE && parent === null) { - // someone can pass in the bits of an array ctx that mean $ - return PredictionContext.EMPTY; - } else { - return new SingletonPredictionContext(parent, returnState); - } - } } diff --git a/src/atn/StarBlockStartState.d.ts b/src/atn/StarBlockStartState.d.ts deleted file mode 100644 index 797fa8f..0000000 --- a/src/atn/StarBlockStartState.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BlockStartState } from "./BlockStartState.js"; - -/** - * The block that begins a closure loop - */ -export class StarBlockStartState extends BlockStartState { -} diff --git a/src/atn/StarBlockStartState.js b/src/atn/StarBlockStartState.ts similarity index 90% rename from src/atn/StarBlockStartState.js rename to src/atn/StarBlockStartState.ts index 020bb36..39979e3 100644 --- a/src/atn/StarBlockStartState.js +++ b/src/atn/StarBlockStartState.ts @@ -11,7 +11,7 @@ import { ATNStateType } from "./ATNStateType.js"; * The block that begins a closure loop */ export class StarBlockStartState extends BlockStartState { - get stateType() { + public override get stateType(): number { return ATNStateType.STAR_BLOCK_START; } diff --git a/src/atn/StarLoopEntryState.js b/src/atn/StarLoopEntryState.js deleted file mode 100644 index efbf199..0000000 --- a/src/atn/StarLoopEntryState.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DecisionState } from "./DecisionState.js"; -import { ATNStateType } from "./ATNStateType.js"; - -export class StarLoopEntryState extends DecisionState { - constructor() { - super(); - this.precedenceRuleDecision = false; - } - - get stateType() { - return ATNStateType.STAR_LOOP_ENTRY; - } - -} diff --git a/src/atn/StarLoopEntryState.d.ts b/src/atn/StarLoopEntryState.ts similarity index 80% rename from src/atn/StarLoopEntryState.d.ts rename to src/atn/StarLoopEntryState.ts index 4b4baec..e7c9e32 100644 --- a/src/atn/StarLoopEntryState.d.ts +++ b/src/atn/StarLoopEntryState.ts @@ -5,6 +5,7 @@ */ import { DecisionState } from "./DecisionState.js"; +import { ATNStateType } from "./ATNStateType.js"; import { StarLoopbackState } from "./StarLoopbackState.js"; import type { ParserATNSimulator } from "./ParserATNSimulator.js"; @@ -26,4 +27,13 @@ export class StarLoopEntryState extends DecisionState { */ public precedenceRuleDecision: boolean; + public constructor() { + super(); + this.precedenceRuleDecision = false; + } + + public override get stateType(): number { + return ATNStateType.STAR_LOOP_ENTRY; + } + } diff --git a/src/atn/StarLoopbackState.d.ts b/src/atn/StarLoopbackState.d.ts deleted file mode 100644 index 8ec3d3d..0000000 --- a/src/atn/StarLoopbackState.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "./ATNState.js"; - -export declare class StarLoopbackState extends ATNState { -} diff --git a/src/atn/StarLoopbackState.js b/src/atn/StarLoopbackState.ts similarity index 89% rename from src/atn/StarLoopbackState.js rename to src/atn/StarLoopbackState.ts index 5eb6afc..7a40d46 100644 --- a/src/atn/StarLoopbackState.js +++ b/src/atn/StarLoopbackState.ts @@ -8,8 +8,7 @@ import { ATNState } from "./ATNState.js"; import { ATNStateType } from "./ATNStateType.js"; export class StarLoopbackState extends ATNState { - get stateType() { + public override get stateType(): number { return ATNStateType.STAR_LOOP_BACK; } - } diff --git a/src/atn/TokensStartState.d.ts b/src/atn/TokensStartState.d.ts deleted file mode 100644 index 52e4e94..0000000 --- a/src/atn/TokensStartState.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DecisionState } from "./DecisionState.js"; - -/** - * The Tokens rule start state linking to each lexer rule start state - */ -export declare class TokensStartState extends DecisionState { -} diff --git a/src/atn/TokensStartState.js b/src/atn/TokensStartState.ts similarity index 91% rename from src/atn/TokensStartState.js rename to src/atn/TokensStartState.ts index 0597f6c..58482cb 100644 --- a/src/atn/TokensStartState.js +++ b/src/atn/TokensStartState.ts @@ -11,7 +11,7 @@ import { ATNStateType } from "./ATNStateType.js"; * The Tokens rule start state linking to each lexer rule start state */ export class TokensStartState extends DecisionState { - get stateType() { + public override get stateType(): number { return ATNStateType.TOKEN_START; } diff --git a/src/atn/Transition.d.ts b/src/atn/Transition.d.ts deleted file mode 100644 index b6a9ae9..0000000 --- a/src/atn/Transition.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { IntervalSet } from "../misc/IntervalSet.js"; -import { ATNState } from "../atn/ATNState.js"; -import { TransitionType } from "./TransitionType.js"; - -export declare abstract class Transition { - public isEpsilon: boolean; - public target: ATNState; - public label: IntervalSet | null; - public serializationType: TransitionType; - - public constructor(target: ATNState); - - public abstract matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; -} diff --git a/src/atn/Transition.js b/src/atn/Transition.js deleted file mode 100644 index c85cbe1..0000000 --- a/src/atn/Transition.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * An ATN transition between any two ATN states. Subclasses define - * atom, set, epsilon, action, predicate, rule transitions. - * - *

            This is a one way link. It emanates from a state (usually via a list of - * transitions) and has a target state.

            - * - *

            Since we never have to change the ATN transitions once we construct it, - * we can fix these transitions as specific classes. The DFA transitions - * on the other hand need to update the labels as it adds transitions to - * the states. We'll use the term Edge for the DFA to distinguish them from - * ATN transitions.

            - */ -export class Transition { - constructor(target) { - // The target of this transition. - if (target === undefined || target === null) { - throw "target cannot be null."; - } - this.target = target; - // Are we epsilon, action, sempred? - this.isEpsilon = false; - this.label = null; - } -} diff --git a/src/atn/Transition.ts b/src/atn/Transition.ts new file mode 100644 index 0000000..d17d1ad --- /dev/null +++ b/src/atn/Transition.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { IntervalSet } from "../misc/IntervalSet.js"; +import { ATNState } from "./ATNState.js"; + +/** + * An ATN transition between any two ATN states. Subclasses define + * atom, set, epsilon, action, predicate, rule transitions. + * + *

            This is a one way link. It emanates from a state (usually via a list of + * transitions) and has a target state.

            + * + *

            Since we never have to change the ATN transitions once we construct it, + * we can fix these transitions as specific classes. The DFA transitions + * on the other hand need to update the labels as it adds transitions to + * the states. We'll use the term Edge for the DFA to distinguish them from + * ATN transitions.

            + */ +export abstract class Transition { + public static readonly serializationNames = [ + "INVALID", + "EPSILON", + "RANGE", + "RULE", + "PREDICATE", + "ATOM", + "ACTION", + "SET", + "NOT_SET", + "WILDCARD", + "PRECEDENCE", + ]; + + /** The target of this transition. */ + public target: ATNState; + + public constructor(target: ATNState) { + this.target = target; + } + + /** + * Determines if the transition is an "epsilon" transition. + * + *

            The default implementation returns {@code false}.

            + * + * @returns `true` if traversing this transition in the ATN does not + * consume an input symbol; otherwise, {@code false} if traversing this + * transition consumes (matches) an input symbol. + */ + public get isEpsilon(): boolean { + return false; + } + + public get label(): IntervalSet | null { + return null; + } + + public abstract get serializationType(): number; + public abstract matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; +} diff --git a/src/atn/TransitionType.d.ts b/src/atn/TransitionType.d.ts deleted file mode 100644 index bad44b0..0000000 --- a/src/atn/TransitionType.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/* eslint-disable @typescript-eslint/naming-convention */ - -// Not using an enum here, as this creates trouble with isolated modules. -/** constants for serialization */ -export declare class TransitionType { - public static readonly EPSILON: number; - public static readonly RANGE: number; - public static readonly RULE: number; - public static readonly PREDICATE: number; // e.g., {isType(input.LT(1))}? - public static readonly ATOM: number; - public static readonly ACTION: number; - public static readonly SET: number; // ~(A|B) or ~atom, wildcard, which convert to next 2 - public static readonly NOT_SET: number; - public static readonly WILDCARD: number; - public static readonly PRECEDENCE: number; -} diff --git a/src/atn/TransitionType.js b/src/atn/TransitionType.ts similarity index 87% rename from src/atn/TransitionType.js rename to src/atn/TransitionType.ts index 39cc962..7e87353 100644 --- a/src/atn/TransitionType.js +++ b/src/atn/TransitionType.ts @@ -4,6 +4,8 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable @typescript-eslint/naming-convention */ + export const TransitionType = { // constants for serialization EPSILON: 1, @@ -16,4 +18,4 @@ export const TransitionType = { NOT_SET: 8, WILDCARD: 9, PRECEDENCE: 10, -}; +} as const; diff --git a/src/atn/WildcardTransition.d.ts b/src/atn/WildcardTransition.d.ts deleted file mode 100644 index 4298056..0000000 --- a/src/atn/WildcardTransition.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNState } from "../atn/ATNState.js"; -import { Transition } from "./Transition.js"; - -export declare class WildcardTransition extends Transition { - public constructor(target: ATNState); - - public matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; - public toString(): string; -} diff --git a/src/atn/WildcardTransition.js b/src/atn/WildcardTransition.ts similarity index 59% rename from src/atn/WildcardTransition.js rename to src/atn/WildcardTransition.ts index 2f649eb..6fd4704 100644 --- a/src/atn/WildcardTransition.js +++ b/src/atn/WildcardTransition.ts @@ -4,20 +4,24 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ATNState } from "./ATNState.js"; import { Transition } from "./Transition.js"; import { TransitionType } from "./TransitionType.js"; export class WildcardTransition extends Transition { - constructor(target) { + public constructor(target: ATNState) { super(target); - this.serializationType = TransitionType.WILDCARD; } - matches(symbol, minVocabSymbol, maxVocabSymbol) { + public override get serializationType(): number { + return TransitionType.WILDCARD; + } + + public override matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean { return symbol >= minVocabSymbol && symbol <= maxVocabSymbol; } - toString() { + public override toString(): string { return "."; } } diff --git a/src/atn/index.js b/src/atn/index.js deleted file mode 100644 index a020406..0000000 --- a/src/atn/index.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export * from './ATN.js'; -export * from "./ATNConfig.js"; -export * from "./ATNConfigSet.js"; -export * from './ATNDeserializer.js'; -export * from './ATNState.js'; -export * from "./ATNStateType.js"; -export * from "./ActionTransition.js"; -export * from "./ArrayPredictionContext.js"; -export * from "./AtomTransition.js"; -export * from "./BasicState.js"; -export * from "./BlockEndState.js"; -export * from "./BlockStartState.js"; -export * from "./DecisionState.js"; -export * from "./EmptyPredictionContext.js"; -export * from "./EpsilonTransition.js"; -export * from './LexerATNSimulator.js'; -export * from './LexerAction.js'; -export * from './LexerChannelAction.js'; -export * from './LexerIndexedCustomAction.js'; -export * from './LexerModeAction.js'; -export * from './LexerMoreAction.js'; -export * from './LexerPopModeAction.js'; -export * from './LexerPushModeAction.js'; -export * from './LexerSkipAction.js'; -export * from './LexerTypeAction.js'; -export * from "./LoopEndState.js"; -export * from "./NotSetTransition.js"; -export * from './ParserATNSimulator.js'; -export * from "./PlusBlockStartState.js"; -export * from "./PlusLoopbackState.js"; -export * from "./PrecedencePredicateTransition.js"; -export * from "./PredicateTransition.js"; -export * from "./PredictionContext.js"; -export * from './PredictionContextCache.js'; -export * from './PredictionMode.js'; -export * from "./RangeTransition.js"; -export * from "./RuleContext.js"; -export * from "./RuleStartState.js"; -export * from "./RuleStopState.js"; -export * from "./RuleTransition.js"; -export * from "./SetTransition.js"; -export * from "./SingletonPredictionContext.js"; -export * from "./StarBlockStartState.js"; -export * from "./StarLoopEntryState.js"; -export * from "./StarLoopbackState.js"; -export * from "./TokensStartState.js"; -export * from "./Transition.js"; -export * from "./TransitionType.js"; -export * from "./WildcardTransition.js"; diff --git a/src/atn/index.d.ts b/src/atn/index.ts similarity index 62% rename from src/atn/index.d.ts rename to src/atn/index.ts index 16d8736..3ff49b3 100644 --- a/src/atn/index.d.ts +++ b/src/atn/index.ts @@ -4,61 +4,63 @@ * can be found in the LICENSE.txt file in the project root. */ -export * from "./LexerAction.js"; -// export * from "./LexerChannelAction.js"; -// export * from "./LexerIndexedCustomAction.js"; -// export * from "./LexerModeAction.js"; -// export * from "./LexerMoreAction.js"; -// export * from "./LexerPopModeAction.js"; -// export * from "./LexerPushModeAction.js"; -// export * from "./LexerSkipAction.js"; -// export * from "./LexerTypeAction.js"; - +export * from "./AbstractPredicateTransition.js"; +export * from "./ActionTransition.js"; +export * from "./ArrayPredictionContext.js"; export * from "./ATN.js"; export * from "./ATNConfig.js"; export * from "./ATNConfigSet.js"; +export * from "./ATNDeserializationOptions.js"; export * from "./ATNDeserializer.js"; export * from "./ATNSimulator.js"; export * from "./ATNState.js"; export * from "./ATNStateType.js"; +export * from "./AtomTransition.js"; +export * from "./BasicBlockStartState.js"; export * from "./BasicState.js"; -export * from "./BlockStartState.js"; export * from "./BlockEndState.js"; +export * from "./BlockStartState.js"; export * from "./DecisionState.js"; +export * from "./EmptyPredictionContext.js"; +export * from "./EpsilonTransition.js"; +export * from "./LexerAction.js"; +export * from "./LexerActionExecutor.js"; +export * from "./LexerActionType.js"; +export * from "./LexerATNConfig.js"; +export * from "./LexerATNSimulator.js"; +export * from "./LexerChannelAction.js"; +export * from "./LexerCustomAction.js"; +export * from "./LexerIndexedCustomAction.js"; +export * from "./LexerModeAction.js"; +export * from "./LexerMoreAction.js"; +export * from "./LexerPopModeAction.js"; +export * from "./LexerPushModeAction.js"; +export * from "./LexerSkipAction.js"; +export * from "./LexerTypeAction.js"; +export * from "./LL1Analyzer.js"; export * from "./LoopEndState.js"; +export * from "./NotSetTransition.js"; +export * from "./OrderedATNConfigSet.js"; +export * from "./ParserATNSimulator.js"; export * from "./PlusBlockStartState.js"; export * from "./PlusLoopbackState.js"; -export * from "./RuleStartState.js"; -export * from "./RuleStopState.js"; -export * from "./StarBlockStartState.js"; -export * from "./StarLoopbackState.js"; -export * from "./StarLoopEntryState.js"; -export * from "./TokensStartState.js"; -export * from "./LexerATNSimulator.js"; -export * from "./ParserATNSimulator.js"; +export * from "./PrecedencePredicateTransition.js"; +export * from "./PredicateTransition.js"; +export * from "./PredictionContext.js"; export * from "./PredictionContextCache.js"; +export * from "./PredictionContextUtils.js"; export * from "./PredictionMode.js"; - -//export * from "./ArrayPredictionContext.js"; -//export * from "./EmptyPredictionContext.js"; -export * from "../ParserRuleContext.js"; -//export * from "./PredictionContext.js"; -export * from "./RuleContext.js"; -//export * from "./SingletonPredictionContext.js"; - -export * from "./DecisionState.js"; +export * from "./RangeTransition.js"; export * from "./RuleStartState.js"; export * from "./RuleStopState.js"; - -export * from "./ActionTransition.js"; -export * from "./AtomTransition.js"; -export * from "./EpsilonTransition.js"; -export * from "./NotSetTransition.js"; -export * from "./PrecedencePredicateTransition.js"; -export * from "./PredicateTransition.js"; -export * from "./RangeTransition.js"; export * from "./RuleTransition.js"; +export * from "./SemanticContext.js"; export * from "./SetTransition.js"; +export * from "./SingletonPredictionContext.js"; +export * from "./StarBlockStartState.js"; +export * from "./StarLoopbackState.js"; +export * from "./StarLoopEntryState.js"; +export * from "./TokensStartState.js"; export * from "./Transition.js"; export * from "./TransitionType.js"; export * from "./WildcardTransition.js"; diff --git a/src/dfa/DFA.d.ts b/src/dfa/DFA.d.ts deleted file mode 100644 index dd53397..0000000 --- a/src/dfa/DFA.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class DFA { - public constructor(ds: unknown, index: number); - - public toLexerString(): string; -} diff --git a/src/dfa/DFA.js b/src/dfa/DFA.js deleted file mode 100644 index 8067bfc..0000000 --- a/src/dfa/DFA.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { DFAState } from './DFAState.js'; -import { StarLoopEntryState } from '../atn/StarLoopEntryState.js'; -import { ATNConfigSet } from './../atn/ATNConfigSet.js'; -import { DFASerializer } from './DFASerializer.js'; -import { LexerDFASerializer } from './LexerDFASerializer.js'; -import { HashSet } from "../misc/HashSet.js"; - -export class DFA { - constructor(atnStartState, decision) { - if (decision === undefined) { - decision = 0; - } - /** - * From which ATN state did we create this DFA? - */ - this.atnStartState = atnStartState; - this.decision = decision; - /** - * A set of all DFA states. Use {@link Map} so we can get old state back - * ({@link Set} only allows you to see if it's there). - */ - this._states = new HashSet(); - this.s0 = null; - /** - * {@code true} if this DFA is for a precedence decision; otherwise, - * {@code false}. This is the backing field for {@link //isPrecedenceDfa}, - * {@link //setPrecedenceDfa} - */ - this.precedenceDfa = false; - if (atnStartState instanceof StarLoopEntryState) { - if (atnStartState.precedenceRuleDecision) { - this.precedenceDfa = true; - const precedenceState = new DFAState(null, new ATNConfigSet()); - precedenceState.edges = []; - precedenceState.isAcceptState = false; - precedenceState.requiresFullContext = false; - this.s0 = precedenceState; - } - } - } - - /** - * Get the start state for a specific precedence value. - * - * @param precedence The current precedence. - * @return The start state corresponding to the specified precedence, or - * {@code null} if no start state exists for the specified precedence. - * - * @throws IllegalStateException if this is not a precedence DFA. - * @see //isPrecedenceDfa() - */ - getPrecedenceStartState(precedence) { - if (!(this.precedenceDfa)) { - throw ("Only precedence DFAs may contain a precedence start state."); - } - // s0.edges is never null for a precedence DFA - if (precedence < 0 || precedence >= this.s0.edges.length) { - return null; - } - return this.s0.edges[precedence] || null; - } - - /** - * Set the start state for a specific precedence value. - * - * @param precedence The current precedence. - * @param startState The start state corresponding to the specified - * precedence. - * - * @throws IllegalStateException if this is not a precedence DFA. - * @see //isPrecedenceDfa() - */ - setPrecedenceStartState(precedence, startState) { - if (!(this.precedenceDfa)) { - throw ("Only precedence DFAs may contain a precedence start state."); - } - if (precedence < 0) { - return; - } - - /** - * synchronization on s0 here is ok. when the DFA is turned into a - * precedence DFA, s0 will be initialized once and not updated again - * s0.edges is never null for a precedence DFA - */ - this.s0.edges[precedence] = startState; - } - - /** - * Sets whether this is a precedence DFA. If the specified value differs - * from the current DFA configuration, the following actions are taken; - * otherwise no changes are made to the current DFA. - * - *
              - *
            • The {@link //states} map is cleared
            • - *
            • If {@code precedenceDfa} is {@code false}, the initial state - * {@link //s0} is set to {@code null}; otherwise, it is initialized to a new - * {@link DFAState} with an empty outgoing {@link DFAState//edges} array to - * store the start states for individual precedence values.
            • - *
            • The {@link //precedenceDfa} field is updated
            • - *
            - * - * @param precedenceDfa {@code true} if this is a precedence DFA; otherwise, - * {@code false} - */ - setPrecedenceDfa(precedenceDfa) { - if (this.precedenceDfa !== precedenceDfa) { - this._states = new HashSet(); - if (precedenceDfa) { - const precedenceState = new DFAState(null, new ATNConfigSet()); - precedenceState.edges = []; - precedenceState.isAcceptState = false; - precedenceState.requiresFullContext = false; - this.s0 = precedenceState; - } else { - this.s0 = null; - } - this.precedenceDfa = precedenceDfa; - } - } - - /** - * Return a list of all states in this DFA, ordered by state number. - */ - sortedStates() { - const list = this._states.values(); - return list.sort(function (a, b) { - return a.stateNumber - b.stateNumber; - }); - } - - toString(literalNames, symbolicNames) { - literalNames = literalNames || null; - symbolicNames = symbolicNames || null; - if (this.s0 === null) { - return ""; - } - const serializer = new DFASerializer(this, literalNames, symbolicNames); - return serializer.toString(); - } - - toLexerString() { - if (this.s0 === null) { - return ""; - } - const serializer = new LexerDFASerializer(this); - return serializer.toString(); - } - - get states() { - return this._states; - } -} diff --git a/src/dfa/DFA.ts b/src/dfa/DFA.ts new file mode 100644 index 0000000..fbc37bf --- /dev/null +++ b/src/dfa/DFA.ts @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { DFASerializer } from "./DFASerializer.js"; +import { DFAState } from "./DFAState.js"; +import { LexerDFASerializer } from "./LexerDFASerializer.js"; +import { Vocabulary } from "../Vocabulary.js"; +import { DecisionState } from "../atn/DecisionState.js"; +import { StarLoopEntryState } from "../atn/StarLoopEntryState.js"; +import { HashSet } from "../misc/HashSet.js"; + +export class DFA { + /** + * A set of all DFA states. Use {@link Map} so we can get old state back + * ({@link Set} only allows you to see if it's there). + */ + + public readonly states = new HashSet(); + + public s0: DFAState | null = null; + + public readonly decision: number; + + /** From which ATN state did we create this DFA? */ + + public readonly atnStartState: DecisionState | null; + + /** + * {@code true} if this DFA is for a precedence decision; otherwise, + * {@code false}. This is the backing field for {@link #isPrecedenceDfa}. + */ + public readonly precedenceDfa: boolean; + + public constructor(atnStartState: DecisionState | null, decision?: number) { + this.atnStartState = atnStartState; + this.decision = decision ?? 0; + + let precedenceDfa = false; + if (atnStartState instanceof StarLoopEntryState) { + if (atnStartState.precedenceRuleDecision) { + precedenceDfa = true; + const precedenceState = new DFAState(); + precedenceState.edges = []; + precedenceState.isAcceptState = false; + precedenceState.requiresFullContext = false; + this.s0 = precedenceState; + } + } + + this.precedenceDfa = precedenceDfa; + } + + /** + * Gets whether this DFA is a precedence DFA. Precedence DFAs use a special + * start state {@link #s0} which is not stored in {@link #states}. The + * {@link DFAState#edges} array for this start state contains outgoing edges + * supplying individual start states corresponding to specific precedence + * values. + * + @returns `true` if this is a precedence DFA; otherwise, + * {@code false}. + * @see Parser#getPrecedence() + */ + public readonly isPrecedenceDfa = (): boolean => { + return this.precedenceDfa; + }; + + /** + * Get the start state for a specific precedence value. + * + * @param precedence The current precedence. + @returns The start state corresponding to the specified precedence, or + * {@code null} if no start state exists for the specified precedence. + * + * @throws IllegalStateException if this is not a precedence DFA. + * @see #isPrecedenceDfa() + */ + public readonly getPrecedenceStartState = (precedence: number): DFAState | null => { + if (!this.isPrecedenceDfa()) { + throw new Error(`Only precedence DFAs may contain a precedence start state.`); + } + + // s0.edges is never null for a precedence DFA + if (!this.s0 || !this.s0.edges || precedence < 0 || precedence >= this.s0.edges.length) { + return null; + } + + return this.s0.edges[precedence]; + }; + + /** + * Set the start state for a specific precedence value. + * + * @param precedence The current precedence. + * @param startState The start state corresponding to the specified + * precedence. + * + * @throws IllegalStateException if this is not a precedence DFA. + * @see #isPrecedenceDfa() + */ + public readonly setPrecedenceStartState = (precedence: number, startState: DFAState): void => { + if (!this.isPrecedenceDfa()) { + throw new Error(`Only precedence DFAs may contain a precedence start state.`); + } + + if (precedence < 0 || !this.s0?.edges) { + return; + } + + // synchronization on s0 here is ok. when the DFA is turned into a + // precedence DFA, s0 will be initialized once and not updated again + // s0.edges is never null for a precedence DFA + if (precedence >= this.s0.edges.length) { + const start = this.s0.edges.length; + this.s0.edges.length = precedence + 1; + this.s0.edges.fill(null, start, precedence); + } + + this.s0.edges[precedence] = startState; + }; + + /** + * Sets whether this is a precedence DFA. + * + * @param precedenceDfa {@code true} if this is a precedence DFA; otherwise, + * {@code false} + * + * @throws UnsupportedOperationException if {@code precedenceDfa} does not + * match the value of {@link #isPrecedenceDfa} for the current DFA. + * + * @deprecated This method no longer performs any action. + */ + public setPrecedenceDfa(precedenceDfa: boolean): void { + if (precedenceDfa !== this.isPrecedenceDfa()) { + throw new Error( + `The precedenceDfa field cannot change after a DFA is constructed.`); + } + }; + + /** + * @returns a list of all states in this DFA, ordered by state number. + */ + public getStates(): DFAState[] { + const result = this.states.values(); + result.sort((o1: DFAState, o2: DFAState): number => { + return o1.stateNumber - o2.stateNumber; + }); + + return result; + }; + + public toString(vocabulary?: Vocabulary): string { + if (!vocabulary) { + return this.toString(Vocabulary.EMPTY_VOCABULARY); + } + + if (this.s0 === null) { + return ""; + } + + const serializer = new DFASerializer(this, vocabulary); + + return serializer.toString() ?? ""; + } + + public toLexerString(): string { + if (this.s0 === null) { + return ""; + } + + const serializer = new LexerDFASerializer(this); + + return serializer.toString() ?? ""; + }; + +} diff --git a/src/dfa/DFASerializer.js b/src/dfa/DFASerializer.js deleted file mode 100644 index 4be1455..0000000 --- a/src/dfa/DFASerializer.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { arrayToString } from "../utils/arrayToString.js"; - -/** - * A DFA walker that knows how to dump them to serialized strings. - */ -export class DFASerializer { - constructor(dfa, literalNames, symbolicNames) { - this.dfa = dfa; - - // XXX: switch to vocabulary. - this.literalNames = literalNames || []; - this.symbolicNames = symbolicNames || []; - } - - toString() { - if (this.dfa.s0 === null) { - return null; - } - let buf = ""; - const states = this.dfa.sortedStates(); - for (let i = 0; i < states.length; i++) { - const s = states[i]; - if (s.edges !== null) { - const n = s.edges.length; - for (let j = 0; j < n; j++) { - const t = s.edges[j] || null; - if (t !== null && t.stateNumber !== 0x7FFFFFFF) { - buf = buf.concat(this.getStateString(s)); - buf = buf.concat("-"); - buf = buf.concat(this.getEdgeLabel(j)); - buf = buf.concat("->"); - buf = buf.concat(this.getStateString(t)); - buf = buf.concat('\n'); - } - } - } - } - return buf.length === 0 ? null : buf; - } - - getEdgeLabel(i) { - if (i === 0) { - return "EOF"; - } else if (this.literalNames !== null || this.symbolicNames !== null) { - return this.literalNames[i - 1] || this.symbolicNames[i - 1]; - } else { - return String.fromCharCode(i - 1); - } - } - - getStateString(s) { - const baseStateStr = (s.isAcceptState ? ":" : "") + "s" + s.stateNumber + (s.requiresFullContext ? "^" : ""); - if (s.isAcceptState) { - if (s.predicates !== null) { - return baseStateStr + "=>" + arrayToString(s.predicates); - } else { - return baseStateStr + "=>" + s.prediction.toString(); - } - } else { - return baseStateStr; - } - } -} diff --git a/src/dfa/DFASerializer.ts b/src/dfa/DFASerializer.ts new file mode 100644 index 0000000..a55a87f --- /dev/null +++ b/src/dfa/DFASerializer.ts @@ -0,0 +1,73 @@ +/* java2ts: keep */ + +/* + * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { DFA } from "./DFA.js"; +import { DFAState } from "./DFAState.js"; +import { Vocabulary } from "../Vocabulary.js"; + +/** A DFA walker that knows how to dump them to serialized strings. */ +export class DFASerializer { + + private readonly dfa: DFA; + private readonly vocabulary: Vocabulary; + + public constructor(dfa: DFA, vocabulary: Vocabulary) { + this.dfa = dfa; + this.vocabulary = vocabulary; + } + + public toString(): string { + if (this.dfa.s0 === null) { + return ""; + } + + let buf = ""; + const states = this.dfa.getStates(); + for (const s of states) { + let n = 0; + if (s.edges !== null) { + n = s.edges.length; + + for (let i = 0; i < n; i++) { + const t = s.edges[i]; + if (t !== null && t.stateNumber !== 0x7FFFFFFF) { + buf += this.getStateString(s); + const label = this.getEdgeLabel(i); + buf += "-"; + buf += label; + buf += "->"; + buf += this.getStateString(t); + buf += "\n"; + } + } + } + } + + return buf; + }; + + protected getEdgeLabel(i: number): string { + const name = this.vocabulary.getDisplayName(i - 1); + + return `${name}`; + }; + + protected getStateString(s: DFAState): string { + const n = s.stateNumber; + const baseStateStr = (s.isAcceptState ? ":" : "") + "s" + n + (s.requiresFullContext ? "^" : ""); + if (s.isAcceptState) { + if (s.predicates !== null) { + return `${baseStateStr}=>${s.predicates.toString()}`; + } else { + return `${baseStateStr}=>${s.prediction}`; + } + } else { + return `${baseStateStr}`; + } + }; +} diff --git a/src/dfa/DFAState.js b/src/dfa/DFAState.js deleted file mode 100644 index 000efde..0000000 --- a/src/dfa/DFAState.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ATNConfigSet } from '../atn/ATNConfigSet.js'; -import { HashCode } from "../misc/HashCode.js"; -import { HashSet } from "../misc/HashSet.js"; - -/** - * A DFA state represents a set of possible ATN configurations. - * As Aho, Sethi, Ullman p. 117 says "The DFA uses its state - * to keep track of all possible states the ATN can be in after - * reading each input symbol. That is to say, after reading - * input a1a2..an, the DFA is in a state that represents the - * subset T of the states of the ATN that are reachable from the - * ATN's start state along some path labeled a1a2..an." - * In conventional NFA→DFA conversion, therefore, the subset T - * would be a bitset representing the set of states the - * ATN could be in. We need to track the alt predicted by each - * state as well, however. More importantly, we need to maintain - * a stack of states, tracking the closure operations as they - * jump from rule to rule, emulating rule invocations (method calls). - * I have to add a stack to simulate the proper lookahead sequences for - * the underlying LL grammar from which the ATN was derived. - * - *

            I use a set of ATNConfig objects not simple states. An ATNConfig - * is both a state (ala normal conversion) and a RuleContext describing - * the chain of rules (if any) followed to arrive at that state.

            - * - *

            A DFA state may have multiple references to a particular state, - * but with different ATN contexts (with same or different alts) - * meaning that state was reached via a different set of rule invocations.

            - */ -export class DFAState { - constructor(stateNumber, configs) { - if (stateNumber === null) { - stateNumber = -1; - } - if (configs === null) { - configs = new ATNConfigSet(); - } - this.stateNumber = stateNumber; - this.configs = configs; - /** - * {@code edges[symbol]} points to target of symbol. Shift up by 1 so (-1) - * {@link Token//EOF} maps to {@code edges[0]}. - */ - this.edges = null; - this.isAcceptState = false; - /** - * if accept state, what ttype do we match or alt do we predict? - * This is set to {@link ATN//INVALID_ALT_NUMBER} when {@link//predicates} - * {@code !=null} or {@link //requiresFullContext}. - */ - this.prediction = 0; - this.lexerActionExecutor = null; - /** - * Indicates that this state was created during SLL prediction that - * discovered a conflict between the configurations in the state. Future - * {@link ParserATNSimulator//execATN} invocations immediately jumped doing - * full context prediction if this field is true. - */ - this.requiresFullContext = false; - /** - * During SLL parsing, this is a list of predicates associated with the - * ATN configurations of the DFA state. When we have predicates, - * {@link //requiresFullContext} is {@code false} since full context - * prediction evaluates predicates - * on-the-fly. If this is not null, then {@link //prediction} is - * {@link ATN//INVALID_ALT_NUMBER}. - * - *

            We only use these for non-{@link //requiresFullContext} but - * conflicting states. That - * means we know from the context (it's $ or we don't dip into outer - * context) that it's an ambiguity not a conflict.

            - * - *

            This list is computed by {@link - * ParserATNSimulator//predicateDFAState}.

            - */ - this.predicates = null; - return this; - } - - /** - * Get the set of all alts mentioned by all ATN configurations in this - * DFA state. - */ - getAltSet() { - const alts = new HashSet(); - if (this.configs !== null) { - for (let i = 0; i < this.configs.length; i++) { - const c = this.configs[i]; - alts.add(c.alt); - } - } - if (alts.length === 0) { - return null; - } else { - return alts; - } - } - - /** - * Two {@link DFAState} instances are equal if their ATN configuration sets - * are the same. This method is used to see if a state already exists. - * - *

            Because the number of alternatives and number of ATN configurations are - * finite, there is a finite number of DFA states that can be processed. - * This is necessary to show that the algorithm terminates.

            - * - *

            Cannot test the DFA state numbers here because in - * {@link ParserATNSimulator//addDFAState} we need to know if any other state - * exists that has this exact set of ATN configurations. The - * {@link //stateNumber} is irrelevant.

            - */ - equals(other) { - // compare set of ATN configurations in this set with other - return this === other || - (other instanceof DFAState && - this.configs.equals(other.configs)); - } - - toString() { - let s = "" + this.stateNumber + ":" + this.configs; - if (this.isAcceptState) { - s = s + "=>"; - if (this.predicates !== null) - s = s + this.predicates; - else - s = s + this.prediction; - } - return s; - } - - hashCode() { - const hash = new HashCode(); - hash.update(this.configs); - return hash.finish(); - } -} diff --git a/src/dfa/DFAState.ts b/src/dfa/DFAState.ts new file mode 100644 index 0000000..23a9692 --- /dev/null +++ b/src/dfa/DFAState.ts @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* eslint-disable max-classes-per-file */ + +import { ATNConfigSet } from "../atn/ATNConfigSet.js"; +import { LexerActionExecutor } from "../atn/LexerActionExecutor.js"; +import { SemanticContext } from "../atn/SemanticContext.js"; +import { HashCode } from "../misc/HashCode.js"; +import { arrayToString } from "../utils/helpers.js"; + +/** + * A DFA state represents a set of possible ATN configurations. + * As Aho, Sethi, Ullman p. 117 says "The DFA uses its state + * to keep track of all possible states the ATN can be in after + * reading each input symbol. That is to say, after reading + * input a1a2..an, the DFA is in a state that represents the + * subset T of the states of the ATN that are reachable from the + * ATN's start state along some path labeled a1a2..an." + * In conventional NFA->DFA conversion, therefore, the subset T + * would be a bitset representing the set of states the + * ATN could be in. We need to track the alt predicted by each + * state as well, however. More importantly, we need to maintain + * a stack of states, tracking the closure operations as they + * jump from rule to rule, emulating rule invocations (method calls). + * I have to add a stack to simulate the proper lookahead sequences for + * the underlying LL grammar from which the ATN was derived. + * + *

            I use a set of ATNConfig objects not simple states. An ATNConfig + * is both a state (ala normal conversion) and a RuleContext describing + * the chain of rules (if any) followed to arrive at that state.

            + * + *

            A DFA state may have multiple references to a particular state, + * but with different ATN contexts (with same or different alts) + * meaning that state was reached via a different set of rule invocations.

            + */ +export class DFAState { + public stateNumber = -1; + + public configs = new ATNConfigSet(); + + /** + * {@code edges[symbol]} points to target of symbol. Shift up by 1 so (-1) + * {@link Token#EOF} maps to {@code edges[0]}. + */ + + public edges: Array | null = null; + + public isAcceptState = false; + + /** + * if accept state, what ttype do we match or alt do we predict? + * This is set to {@link ATN#INVALID_ALT_NUMBER} when {@link #predicates}{@code !=null} or + * {@link #requiresFullContext}. + */ + public prediction = -1; + + public lexerActionExecutor: LexerActionExecutor | null = null; + + /** + * Indicates that this state was created during SLL prediction that + * discovered a conflict between the configurations in the state. Future + * {@link ParserATNSimulator#execATN} invocations immediately jumped doing + * full context prediction if this field is true. + */ + public requiresFullContext = false; + + /** + * During SLL parsing, this is a list of predicates associated with the + * ATN configurations of the DFA state. When we have predicates, + * {@link #requiresFullContext} is {@code false} since full context prediction evaluates predicates + * on-the-fly. If this is not null, then {@link #prediction} is + * {@link ATN#INVALID_ALT_NUMBER}. + * + *

            We only use these for non-{@link #requiresFullContext} but conflicting states. That + * means we know from the context (it's $ or we don't dip into outer + * context) that it's an ambiguity not a conflict.

            + * + *

            This list is computed by {@link ParserATNSimulator#predicateDFAState}.

            + */ + + public predicates: DFAState.PredPrediction[] | null = null; + + public constructor(stateNumber: number); + public constructor(configs?: ATNConfigSet); + public constructor(stateNumberOrConfigs?: number | ATNConfigSet) { + if (stateNumberOrConfigs) { + if (typeof stateNumberOrConfigs === "number") { + this.stateNumber = stateNumberOrConfigs; + } else { + this.configs = stateNumberOrConfigs; + } + } + } + + public hashCode(): number { + const hash = new HashCode(); + hash.update(this.configs.configs); + + return hash.finish(); + }; + + /** + * Two {@link DFAState} instances are equal if their ATN configuration sets + * are the same. This method is used to see if a state already exists. + * + *

            Because the number of alternatives and number of ATN configurations are + * finite, there is a finite number of DFA states that can be processed. + * This is necessary to show that the algorithm terminates.

            + * + *

            Cannot test the DFA state numbers here because in + * {@link ParserATNSimulator#addDFAState} we need to know if any other state + * exists that has this exact set of ATN configurations. The + * {@link #stateNumber} is irrelevant.

            + * + * @param o tbd + * + * @returns tbd + */ + public equals(o: unknown): boolean { + // compare set of ATN configurations in this set with other + if (this === o) { + return true; + } + + if (!(o instanceof DFAState)) { + return false; + } + + if (this.configs === null) { + if (o.configs === null) { + return true; + } + + return false; + } + + return this.configs.equals(o.configs); + }; + + public toString(): string { + let buf = ""; + buf += this.stateNumber; + buf += ":"; + buf += this.configs ? this.configs.toString() : ""; + if (this.isAcceptState) { + buf += "=>"; + if (this.predicates) { + buf += arrayToString(this.predicates); + } else { + buf += this.prediction; + } + } + + return buf.toString(); + }; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace, no-redeclare +export namespace DFAState { + /** Map a predicate to a predicted alternative. */ + export class PredPrediction { + + public pred: SemanticContext; // never null; at least SemanticContext.NONE + public alt: number; + + public constructor(pred: SemanticContext, alt: number) { + this.alt = alt; + this.pred = pred; + } + + public toString(): string { + return `(${this.pred}, ${this.alt})`; + }; + }; + +} diff --git a/src/dfa/LexerDFASerializer.js b/src/dfa/LexerDFASerializer.ts similarity index 61% rename from src/dfa/LexerDFASerializer.js rename to src/dfa/LexerDFASerializer.ts index 002b018..63cb19c 100644 --- a/src/dfa/LexerDFASerializer.js +++ b/src/dfa/LexerDFASerializer.ts @@ -4,14 +4,16 @@ * can be found in the LICENSE.txt file in the project root. */ +import { Vocabulary } from "../Vocabulary.js"; +import { DFA } from "./DFA.js"; import { DFASerializer } from "./DFASerializer.js"; export class LexerDFASerializer extends DFASerializer { - constructor(dfa) { - super(dfa, null); + public constructor(dfa: DFA) { + super(dfa, Vocabulary.EMPTY_VOCABULARY); } - getEdgeLabel(i) { + public override getEdgeLabel = (i: number): string => { return "'" + String.fromCharCode(i) + "'"; - } + }; } diff --git a/src/dfa/PredPrediction.js b/src/dfa/PredPrediction.js deleted file mode 100644 index 5b51203..0000000 --- a/src/dfa/PredPrediction.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** - * Map a predicate to a predicted alternative. - */ -export class PredPrediction { - constructor(pred, alt) { - this.alt = alt; - this.pred = pred; - } - - toString() { - return "(" + this.pred + ", " + this.alt + ")"; - } -} diff --git a/src/dfa/index.d.ts b/src/dfa/index.d.ts deleted file mode 100644 index 9caf540..0000000 --- a/src/dfa/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export * from "./DFA.js"; diff --git a/src/dfa/index.js b/src/dfa/index.js deleted file mode 100644 index 6ba5f62..0000000 --- a/src/dfa/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export { DFA } from './DFA.js'; -export { DFASerializer } from './DFASerializer.js'; -export { LexerDFASerializer } from './LexerDFASerializer.js'; -export { PredPrediction } from './PredPrediction.js'; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index f36d878..0000000 --- a/src/index.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Token } from './Token.js'; -import { TokenFactory } from "./TokenFactory.js"; -import { CommonToken } from './CommonToken.js'; -import { InputStream } from './InputStream.js'; -import { CharStream } from './CharStream.js'; -import { CharStreams } from './CharStreams.js'; -import { CommonTokenStream } from './CommonTokenStream.js'; -import { Lexer } from './Lexer.js'; -import { Parser } from './Parser.js'; -import { TokenStream } from './TokenStream.js'; -import { TokenStreamRewriter } from './TokenStreamRewriter.js'; -import { Vocabulary } from "./Vocabulary.js"; -import { LexerInterpreter } from "./LexerInterpreter.js"; -import { ParserInterpreter } from "./ParserInterpreter.js"; -import { InterpreterRuleContext } from "./InterpreterRuleContext.js"; - -import { RuleContext } from './atn/RuleContext.js'; -import { ParserRuleContext } from './ParserRuleContext.js'; -import { ATN } from './atn/ATN.js'; -import { ATNState } from "./atn/ATNState.js"; - -import { BlockStartState } from "./atn/BlockStartState.js"; -import { BasicState } from "./atn/BasicState.js"; -import { DecisionState } from "./atn/DecisionState.js"; -import { PlusBlockStartState } from "./atn/PlusBlockStartState.js"; -import { StarBlockStartState } from "./atn/StarBlockStartState.js"; -import { StarLoopEntryState } from "./atn/StarLoopEntryState.js"; -import { PlusLoopbackState } from "./atn/PlusLoopbackState.js"; -import { StarLoopbackState } from "./atn/StarLoopbackState.js"; -import { LoopEndState } from "./atn/LoopEndState.js"; -import { TokensStartState } from "./atn/TokensStartState.js"; - -import { ATNConfig } from "./atn/ATNConfig.js"; -import { ATNConfigSet } from "./atn/ATNConfigSet.js"; -import { PredictionMode } from './atn/PredictionMode.js'; -import { LL1Analyzer } from './atn/LL1Analyzer.js'; -import { ATNDeserializer } from './atn/ATNDeserializer.js'; -import { ATNSimulator } from "./atn/ATNSimulator.js"; -import { LexerATNSimulator } from './atn/LexerATNSimulator.js'; -import { ParserATNSimulator } from './atn/ParserATNSimulator.js'; -import { PredictionContextCache } from './atn/PredictionContextCache.js'; -import { ATNStateType } from "./atn/ATNStateType.js"; -import { NotSetTransition } from "./atn/NotSetTransition.js"; - -import { DFA } from "./dfa/DFA.js"; - -import { RecognitionException } from "./RecognitionException.js"; -import { FailedPredicateException } from "./FailedPredicateException.js"; -import { NoViableAltException } from "./NoViableAltException.js"; -import { BailErrorStrategy } from "./BailErrorStrategy.js"; -import { ParseCancellationException } from "./ParseCancellationException.js"; -import { LexerNoViableAltException } from "./LexerNoViableAltException.js"; -import { DefaultErrorStrategy } from "./DefaultErrorStrategy.js"; -import { BaseErrorListener } from "./BaseErrorListener.js"; -import { DiagnosticErrorListener } from "./DiagnosticErrorListener.js"; -import { InputMismatchException } from "./InputMismatchException.js"; - -import { Interval } from './misc/Interval.js'; -import { IntervalSet } from './misc/IntervalSet.js'; -import { ParseTreeListener } from "./tree/ParseTreeListener.js"; -import { ParseTreeVisitor } from "./tree/ParseTreeVisitor.js"; -import { ParseTreeWalker } from "./tree/ParseTreeWalker.js"; -import { TerminalNode } from "./tree/TerminalNode.js"; -import { ErrorNode } from "./tree/ErrorNode.js"; - -import { XPath } from "./tree/xpath/XPath.js"; - -import { arrayToString } from "./utils/arrayToString.js"; - -import { Transition } from "./atn/Transition.js"; -import { TransitionType } from "./atn/TransitionType.js"; - -export default { - Token, CommonToken, CharStreams, CharStream, InputStream, CommonTokenStream, Lexer, Parser, - TerminalNode, ParseTreeWalker, RuleContext, ParserRuleContext, Interval, IntervalSet, - PredictionMode, LL1Analyzer, ParseTreeListener, ParseTreeVisitor, ATN, ATNState, ATNStateType, ATNConfig, ATNConfigSet, - ATNDeserializer, LexerInterpreter, ParserInterpreter, InterpreterRuleContext, - PredictionContextCache, LexerATNSimulator, ParserATNSimulator, DFA, RecognitionException, NoViableAltException, - FailedPredicateException, BaseErrorListener, DiagnosticErrorListener, BailErrorStrategy, DefaultErrorStrategy, LexerNoViableAltException, - ParseCancellationException, arrayToString, Vocabulary, TokenStream, Transition, TransitionType, TokenFactory, - XPath, ATNSimulator, TokenStreamRewriter, ErrorNode, InputMismatchException, - BlockStartState, BasicState, DecisionState, PlusBlockStartState, StarBlockStartState, StarLoopEntryState, - PlusLoopbackState, StarLoopbackState, LoopEndState, TokensStartState, NotSetTransition -}; - -export { - Token, CommonToken, CharStreams, CharStream, InputStream, CommonTokenStream, Lexer, Parser, - TerminalNode, ParseTreeWalker, RuleContext, ParserRuleContext, Interval, IntervalSet, - PredictionMode, LL1Analyzer, ParseTreeListener, ParseTreeVisitor, ATN, ATNState, ATNStateType, ATNConfig, ATNConfigSet, - ATNDeserializer, LexerInterpreter, ParserInterpreter, InterpreterRuleContext, - PredictionContextCache, LexerATNSimulator, ParserATNSimulator, DFA, RecognitionException, NoViableAltException, - FailedPredicateException, BaseErrorListener, DiagnosticErrorListener, BailErrorStrategy, DefaultErrorStrategy, LexerNoViableAltException, - ParseCancellationException, arrayToString, Vocabulary, TokenStream, Transition, TransitionType, TokenFactory, - XPath, ATNSimulator, TokenStreamRewriter, ErrorNode, InputMismatchException, - BlockStartState, BasicState, DecisionState, PlusBlockStartState, StarBlockStartState, StarLoopEntryState, - PlusLoopbackState, StarLoopbackState, LoopEndState, TokensStartState, NotSetTransition -}; diff --git a/src/index.d.ts b/src/index.ts similarity index 73% rename from src/index.d.ts rename to src/index.ts index 55c0c31..645054e 100644 --- a/src/index.d.ts +++ b/src/index.ts @@ -4,7 +4,12 @@ * can be found in the LICENSE.txt file in the project root. */ -export * from "./InputStream.js"; +export * from "./atn/index.js"; + +export { LexerNoViableAltException } from "./LexerNoViableAltException.js"; +export { ParserRuleContext } from "./ParserRuleContext.js"; +export { RuleContext } from "./RuleContext.js"; + export * from "./CharStream.js"; export * from "./CharStreams.js"; export * from "./TokenStream.js"; @@ -33,11 +38,16 @@ export * from "./BailErrorStrategy.js"; export * from "./DefaultErrorStrategy.js"; export * from "./BaseErrorListener.js"; export * from "./DiagnosticErrorListener.js"; -export * from "./ParseCancellationException.js"; export * from "./LexerNoViableAltException.js"; -export * from "./atn/index.js"; -export * from "./dfa/index.js"; -export * from "./misc/index.js"; +export * from "./dfa/DFA.js"; +export * from "./dfa/DFASerializer.js"; +export * from "./dfa/LexerDFASerializer.js"; + +export * from "./misc/Interval.js"; +export * from "./misc/IntervalSet.js"; +export * from "./misc/ParseCancellationException.js"; + export * from "./tree/index.js"; -export * from "./utils/index.js"; + +export * from "./utils/helpers.js"; diff --git a/src/misc/AltDict.js b/src/misc/AltDict.js deleted file mode 100644 index 1911937..0000000 --- a/src/misc/AltDict.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class AltDict { - constructor() { - this.data = {}; - } - - get(key) { - return this.data["k-" + key] || null; - } - - set(key, value) { - this.data["k-" + key] = value; - } - - values() { - return Object.keys(this.data).filter(key => key.startsWith("k-")).map(key => this.data[key], this); - } -} diff --git a/src/misc/BitSet.d.ts b/src/misc/BitSet.d.ts deleted file mode 100644 index b0496af..0000000 --- a/src/misc/BitSet.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare class BitSet implements Iterable { - private data: Uint16Array; - - /** Creates a new bit set. All bits are initially `false`. */ - public constructor(); - - /** - * Sets all of the bits in this `BitSet` to `false`. - */ - public clear(): void; - - /** - * Sets the bit specified by the index to `false`. - * - * @param bitIndex the index of the bit to be cleared - * - * @throws RangeError if the specified index is negative - */ - public clear(bitIndex: number): void; - - /** - * Returns the value of the bit with the specified index. The value is `true` if the bit with the index `bitIndex` - * is currently set in this `BitSet`; otherwise, the result is `false`. - * - * @param bitIndex the bit index - * - * @throws RangeError if the specified index is negative - */ - public get(bitIndex: number): boolean; - - /** - * Returns the number of set bits. - */ - public length(): number; - - /** - * Returns the index of the first bit that is set to `true` that occurs on or after the specified starting index. - * If no such bit exists then undefined is returned. - * - * @param fromIndex the index to start checking from (inclusive) - */ - public nextSetBit(fromIndex: number): number; - - /** - * Performs a logical **OR** of this bit set with the bit set argument. This bit set is modified so that a bit in it - * has the value `true` if and only if it either already had the value `true` or the corresponding bit in the bit - * set argument has the value `true`. - * - * @param set - */ - public or(set: BitSet): void; - - /** - * Sets the bit at the specified index to `true`. - * - * @param bitIndex a bit index - * - * @throws RangeError if the specified index is negative - */ - public set(bitIndex: number): void; - - /** - * Returns a string representation of this bit set. - */ - public toString(): string; - - public [Symbol.iterator](): IterableIterator; - -} diff --git a/src/misc/BitSet.js b/src/misc/BitSet.ts similarity index 55% rename from src/misc/BitSet.js rename to src/misc/BitSet.ts index e004c7a..57a62a2 100644 --- a/src/misc/BitSet.js +++ b/src/misc/BitSet.ts @@ -4,12 +4,51 @@ * can be found in the LICENSE.txt file in the project root. */ -export class BitSet { - constructor() { +export class BitSet implements Iterable{ + private data: number[]; + + /** Creates a new bit set. All bits are initially `false`. */ + public constructor() { this.data = []; } - clear(index) { + public [Symbol.iterator](): IterableIterator { + const length = this.data.length; + let currentIndex = 0; + let currentWord = this.data[currentIndex]; + const words = this.data; + + return { + [Symbol.iterator](): IterableIterator { + return this; + }, + next: (): IteratorResult => { + while (currentIndex < length) { + if (currentWord !== 0) { + const t = currentWord & -currentWord; + const value = (currentIndex << 5) + this.bitCount((t - 1) | 0); + currentWord ^= t; + + return { done: false, value }; + } else { + currentIndex++; + if (currentIndex < length) { + currentWord = words[currentIndex]; + } + } + } + + return { done: true, value: undefined }; + }, + }; + }; + + /** + * Sets a single bit or all of the bits in this `BitSet` to `false`. + * + * @param index the index of the bit to be cleared, or undefined to clear all bits. + */ + public clear(index?: number): void { if (index === undefined) { this.data = []; } else { @@ -18,7 +57,14 @@ export class BitSet { } } - or(set) { + /** + * Performs a logical **OR** of this bit set with the bit set argument. This bit set is modified so that a bit in it + * has the value `true` if and only if it either already had the value `true` or the corresponding bit in the bit + * set argument has the value `true`. + * + * @param set the bit set to be ORed with. + */ + public or(set: BitSet): void { const minCount = Math.min(this.data.length, set.data.length); let k = 0 | 0; for (; k + 7 < minCount; k += 8) { @@ -45,11 +91,22 @@ export class BitSet { } } - get(index) { + /** + * Returns the value of the bit with the specified index. The value is `true` if the bit with the index `bitIndex` + * is currently set in this `BitSet`; otherwise, the result is `false`. + * + * @param index the bit index + * + * @returns the value of the bit with the specified index. + */ + public get(index: number): boolean { return (this.data[index >>> 5] & (1 << index)) !== 0; } - get length() { + /** + * @returns the number of set bits. + */ + public get length(): number { let result = 0; const c = this.data.length; const w = this.data; @@ -60,83 +117,72 @@ export class BitSet { return result; } - values() { - const result = new Array(this.length()); + /** + * @returns an array with indices of set bits. + */ + public values(): number[] { + const result = new Array(this.length); let pos = 0; const length = this.data.length; for (let k = 0; k < length; ++k) { let w = this.data[k]; - while (w != 0) { + while (w !== 0) { const t = w & -w; result[pos++] = (k << 5) + this.bitCount((t - 1) | 0); w ^= t; } } + return result; } - nextSetBit(value) { + /** + * @returns the index of the first bit that is set to `true` that occurs on or after the specified starting index. + * If no such bit exists then undefined is returned. + * + * @param fromIndex the index to start checking from (inclusive) + */ + public nextSetBit(fromIndex: number): number | undefined { // Iterate over all set bits. - for (let index of this) { + for (const index of this) { // Use the first index > than the specified value index. - if (index > value) { + if (index > fromIndex) { return index; } } + + return undefined; // TODO: should be -1 according to JDK docs. } - set(index) { - this.resize(index); - this.data[index >>> 5] |= 1 << index; + /** + * Sets the bit at the specified index to `true`. + * + * @param bitIndex a bit index + */ + public set(bitIndex: number): void { + this.resize(bitIndex); + this.data[bitIndex >>> 5] |= 1 << bitIndex; } - toString() { + /** + * @returns a string representation of this bit set. + */ + public toString(): string { return "{" + this.values().join(", ") + "}"; } - resize(index) { + private resize(index: number): void { const count = (index + 32) >>> 5; for (let i = this.data.length; i < count; i++) { this.data[i] = 0; } }; - bitCount(v) { // a.k.a. hamming weight + private bitCount(v: number): number { // a.k.a. hamming weight v -= (v >>> 1) & 0x55555555; v = (v & 0x33333333) + ((v >>> 2) & 0x33333333); return (((v + (v >>> 4)) & 0xf0f0f0f) * 0x1010101) >>> 24; }; - [Symbol.iterator]() { - const length = this.data.length; - let currentIndex = 0; - let currentWord = this.data[currentIndex]; - let bc = this.bitCount; - let words = this.data; - - return { - [Symbol.iterator]() { - return this; - }, - next() { - while (currentIndex < length) { - if (currentWord !== 0) { - const t = currentWord & -currentWord; - const value = (currentIndex << 5) + bc((t - 1) | 0); - currentWord ^= t; - - return { done: false, value }; - } else { - currentIndex++; - if (currentIndex < length) { - currentWord = words[currentIndex]; - } - } - } - - return { done: true, value: undefined }; - }, - }; - }; } diff --git a/src/misc/HashCode.js b/src/misc/HashCode.js deleted file mode 100644 index 7d3a69c..0000000 --- a/src/misc/HashCode.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { stringHashCode } from "../utils/stringHashCode.js"; - -export class HashCode { - - constructor() { - this.count = 0; - this.hash = 0; - } - - update() { - for (let i = 0; i < arguments.length; i++) { - const value = arguments[i]; - if (value == null) - continue; - if (Array.isArray(value)) - this.update.apply(this, value); - else { - let k = 0; - switch (typeof (value)) { - case 'undefined': - case 'function': - continue; - case 'number': - case 'boolean': - k = value; - break; - case 'string': - k = stringHashCode(value); - break; - default: - if (value.updateHashCode) - value.updateHashCode(this); - else - console.log("No updateHashCode for " + value.toString()); - continue; - } - k = k * 0xCC9E2D51; - k = (k << 15) | (k >>> (32 - 15)); - k = k * 0x1B873593; - this.count = this.count + 1; - let hash = this.hash ^ k; - hash = (hash << 13) | (hash >>> (32 - 13)); - hash = hash * 5 + 0xE6546B64; - this.hash = hash; - } - } - } - - finish() { - let hash = this.hash ^ (this.count * 4); - hash = hash ^ (hash >>> 16); - hash = hash * 0x85EBCA6B; - hash = hash ^ (hash >>> 13); - hash = hash * 0xC2B2AE35; - hash = hash ^ (hash >>> 16); - return hash; - } - - static hashStuff() { - const hash = new HashCode(); - hash.update.apply(hash, arguments); - return hash.finish(); - } -} diff --git a/src/misc/HashCode.ts b/src/misc/HashCode.ts new file mode 100644 index 0000000..fa7f416 --- /dev/null +++ b/src/misc/HashCode.ts @@ -0,0 +1,126 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +export type HashableTypes = string | number | boolean | IHashUpdatable | HashableTypes[] | null; + +export interface IHashUpdatable { + updateHashCode(hash: HashCode): void; +} + +const c1 = 0xCC9E2D51; +const c2 = 0x1B873593; +const r1 = 15; +const r2 = 13; +const m = 5; +const n = 0xE6546B64; + +export class HashCode { + private count: number; + private hash: number; + + public constructor() { + this.count = 0; + this.hash = 0; + } + + public static hashStuff(...values: HashableTypes[]): number { + const hash = new HashCode(); + hash.update(values); + + return hash.finish(); + } + + private static hashString(hash = 0, str: string): number { + let h1 = 0xdeadbeef ^ hash; + let h2 = 0x41c6ce57 ^ hash; + for (const c of str) { // Correctly iterate over surrogate pairs. + const ch = c.charCodeAt(0); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + return Math.imul(4294967296, (2097151 & h2)) + (h1 >>> 0); + } + + public update(...values: HashableTypes[]): void { + for (const value of values) { + if (value == null) { + continue; + } + + if (Array.isArray(value)) { + this.update(...value); + } else { + let k = 0; + switch (typeof (value)) { + case "undefined": + case "function": { + continue; + } + + case "number": { + k = value; + break; + } + + case "boolean": { + k = value ? 1237 : 4327; + break; + } + + case "string": { + k = HashCode.hashString(this.hash, value); + break; + } + + default: { + value.updateHashCode(this); + + continue; + } + } + + this.count = this.count + 1; + k = Math.imul(k, c1); + k = (k << r1) | (k >>> (32 - r1)); + k = Math.imul(k, c2); + + let hash = this.hash ^ k; + hash = (hash << r2) | (hash >>> (32 - r2)); + this.hash = Math.imul(hash, m) + n; + } + } + } + + /** + * Update the internal hash value with a pre-computed hash value. + * + * @param k The pre-computed hash value. + */ + public updateWithHashCode(k: number): void { + this.count = this.count + 1; + k = Math.imul(k, c1); + k = (k << r1) | (k >>> (32 - r1)); + k = Math.imul(k, c2); + + let hash = this.hash ^ k; + hash = (hash << r2) | (hash >>> (32 - r2)); + this.hash = Math.imul(hash, m) + n; + } + + public finish(): number { + let hash = this.hash ^ (this.count * 4); + hash ^= hash >>> 16; + hash = Math.imul(hash, 0x85EBCA6B); + hash ^= hash >>> 13; + hash = Math.imul(hash, 0xC2B2AE35); + hash ^= hash >>> 16; + + return hash; + } +} diff --git a/src/misc/HashMap.js b/src/misc/HashMap.js deleted file mode 100644 index a8c64c7..0000000 --- a/src/misc/HashMap.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { standardEqualsFunction } from "../utils/standardEqualsFunction.js"; -import { standardHashCodeFunction } from "../utils/standardHashCodeFunction.js"; - -const HASH_KEY_PREFIX = "h-"; - -export class HashMap { - - constructor(hashFunction, equalsFunction) { - this.data = {}; - this.hashFunction = hashFunction || standardHashCodeFunction; - this.equalsFunction = equalsFunction || standardEqualsFunction; - } - - set(key, value) { - const hashKey = HASH_KEY_PREFIX + this.hashFunction(key); - if (hashKey in this.data) { - const entries = this.data[hashKey]; - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - if (this.equalsFunction(key, entry.key)) { - const oldValue = entry.value; - entry.value = value; - return oldValue; - } - } - entries.push({ key: key, value: value }); - return value; - } else { - this.data[hashKey] = [{ key: key, value: value }]; - return value; - } - } - - containsKey(key) { - const hashKey = HASH_KEY_PREFIX + this.hashFunction(key); - if (hashKey in this.data) { - const entries = this.data[hashKey]; - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - if (this.equalsFunction(key, entry.key)) - return true; - } - } - return false; - } - - get(key) { - const hashKey = HASH_KEY_PREFIX + this.hashFunction(key); - if (hashKey in this.data) { - const entries = this.data[hashKey]; - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - if (this.equalsFunction(key, entry.key)) - return entry.value; - } - } - return null; - } - - entries() { - return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).flatMap(key => this.data[key], this); - } - - getKeys() { - return this.entries().map(e => e.key); - } - - getValues() { - return this.entries().map(e => e.value); - } - - toString() { - const ss = this.entries().map(e => '{' + e.key + ':' + e.value + '}'); - return '[' + ss.join(", ") + ']'; - } - - get length() { - return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).map(key => this.data[key].length, this).reduce((accum, item) => accum + item, 0); - } -} diff --git a/src/misc/HashMap.ts b/src/misc/HashMap.ts new file mode 100644 index 0000000..d06001d --- /dev/null +++ b/src/misc/HashMap.ts @@ -0,0 +1,100 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { IComparable, standardEqualsFunction, standardHashCodeFunction } from "../utils/helpers.js"; +import { EqualsFunction, HashFunction } from "./HashSet.js"; + +interface Entry { key: Key, value: Value; } + +export class HashMap { + private data: { [key: string]: Array>; }; + private hashFunction: HashFunction; + private equalsFunction: EqualsFunction; + + public constructor(hashFunction?: HashFunction, equalsFunction?: EqualsFunction) { + this.data = {}; + this.hashFunction = hashFunction ?? standardHashCodeFunction; + this.equalsFunction = equalsFunction ?? standardEqualsFunction; + } + + public set(key: Key, value: Value): Value { + const hashKey = this.hashFunction(key); + if (hashKey in this.data) { + const entries = this.data[hashKey]; + for (const entry of entries) { + if (this.equalsFunction(key, entry.key)) { + const oldValue = entry.value; + entry.value = value; + + return oldValue; + } + } + entries.push({ key, value }); + + return value; + } else { + this.data[hashKey] = [{ key, value }]; + + return value; + } + } + + public containsKey(key: Key): boolean { + const hashKey = this.hashFunction(key); + if (hashKey in this.data) { + const entries = this.data[hashKey]; + for (const entry of entries) { + if (this.equalsFunction(key, entry.key)) { + return true; + } + } + } + + return false; + } + + public get(key: Key): Value | null { + const hashKey = this.hashFunction(key); + if (hashKey in this.data) { + const entries = this.data[hashKey]; + for (const entry of entries) { + if (this.equalsFunction(key, entry.key)) { + return entry.value; + } + } + } + + return null; + } + + public entries(): Array> { + return Object.keys(this.data).flatMap((key) => { + return this.data[key]; + }, this); + } + + public getKeys(): Key[] { + return this.entries().map((e) => { return e.key; }); + } + + public getValues(): Value[] { + return this.entries().map((e) => { return e.value; }); + } + + public toString(): string { + const ss = this.entries().map((e) => { return "{" + e.key + ":" + e.value + "}"; }); + + return "[" + ss.join(", ") + "]"; + } + + public get length(): number { + return Object.keys(this.data).map((key) => { + return this.data[key].length; + }, this).reduce((accumulator, item) => { + return accumulator + item; + }, 0); + } +} diff --git a/src/misc/HashSet.js b/src/misc/HashSet.js deleted file mode 100644 index 2f17aae..0000000 --- a/src/misc/HashSet.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { standardHashCodeFunction } from "../utils/standardHashCodeFunction.js"; -import { standardEqualsFunction } from "../utils/standardEqualsFunction.js"; -import { arrayToString } from "../utils/arrayToString.js"; - -const HASH_KEY_PREFIX = "h-"; - -export class HashSet { - - constructor(hashFunction, equalsFunction) { - this.data = {}; - this.hashFunction = hashFunction || standardHashCodeFunction; - this.equalsFunction = equalsFunction || standardEqualsFunction; - } - - add(value) { - const key = HASH_KEY_PREFIX + this.hashFunction(value); - if (key in this.data) { - const values = this.data[key]; - for (let i = 0; i < values.length; i++) { - if (this.equalsFunction(value, values[i])) { - return values[i]; - } - } - values.push(value); - return value; - } else { - this.data[key] = [value]; - return value; - } - } - - has(value) { - return this.get(value) != null; - } - - get(value) { - const key = HASH_KEY_PREFIX + this.hashFunction(value); - if (key in this.data) { - const values = this.data[key]; - for (let i = 0; i < values.length; i++) { - if (this.equalsFunction(value, values[i])) { - return values[i]; - } - } - } - return null; - } - - values() { - return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).flatMap(key => this.data[key], this); - } - - toString() { - return arrayToString(this.values()); - } - - get length() { - return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).map(key => this.data[key].length, this).reduce((accum, item) => accum + item, 0); - } -} diff --git a/src/misc/HashSet.ts b/src/misc/HashSet.ts new file mode 100644 index 0000000..5df1ddb --- /dev/null +++ b/src/misc/HashSet.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { standardHashCodeFunction, standardEqualsFunction, arrayToString, IComparable } from "../utils/helpers.js"; + +export type HashFunction = (a: string | IComparable) => number; +export type EqualsFunction = (a: IComparable | null, b: unknown) => boolean; + +export class HashSet { + private data: { [key: string]: T[]; }; + private hashFunction: HashFunction; + private equalsFunction: EqualsFunction; + + public constructor(hashFunction?: HashFunction, equalsFunction?: EqualsFunction) { + this.data = {}; + this.hashFunction = hashFunction ?? standardHashCodeFunction; + this.equalsFunction = equalsFunction ?? standardEqualsFunction; + } + + public add(value: T): T { + const key = this.hashFunction(value); + if (key in this.data) { + const entries = this.data[key]; + for (const entry of entries) { + if (this.equalsFunction(value, entry)) { + return entry; + } + } + + entries.push(value); + + return value; + } else { + this.data[key] = [value]; + + return value; + } + } + + public has(value: T): boolean { + return this.get(value) != null; + } + + public get(value: T): T | null { + const key = this.hashFunction(value); + if (key in this.data) { + const entries = this.data[key]; + + for (const entry of entries) { + if (this.equalsFunction(value, entry)) { + return entry; + } + } + } + + return null; + } + + public values(): T[] { + return Object.keys(this.data).flatMap((key) => { + return this.data[key]; + }, this); + } + + public toString(): string { + return arrayToString(this.values()); + } + + public get length(): number { + return Object.keys(this.data).map((key) => { + return this.data[key].length; + }, this).reduce((accumulator, item) => { + return accumulator + item; + }, 0); + } +} diff --git a/src/misc/Interval.d.ts b/src/misc/Interval.d.ts deleted file mode 100644 index 30c8778..0000000 --- a/src/misc/Interval.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -/** An immutable inclusive interval a..b */ -export declare class Interval { - public start: number; - public stop: number; - - public constructor(start: number, stop: number); - - /** - * Interval objects are used readonly so share all with the - * same single value a==b up to some max size. Use an array as a perfect hash. - * Return shared object for 0..INTERVAL_POOL_MAX_VALUE or a new - * Interval object with a..a in it. On Java.g4, 218623 IntervalSets - * have a..a (set with 1 element). - */ - public static of(a: number, b: number): Interval; - - public equals(o: unknown): boolean; - public hashCode(): number; - - /** Does this start completely before other? Disjoint */ - public startsBeforeDisjoint(other: Interval): boolean; - - /** Does this start at or before other? Nondisjoint */ - public startsBeforeNonDisjoint(other: Interval): boolean; - - /** Does this.start start after other.stop? May or may not be disjoint */ - public startsAfter(other: Interval): boolean; - - /** Does this start completely after other? Disjoint */ - public startsAfterDisjoint(other: Interval): boolean; - - /** Does this start after other? NonDisjoint */ - public startsAfterNonDisjoint(other: Interval): boolean; - - /** Are both ranges disjoint? I.e., no overlap? */ - public disjoint(other: Interval): boolean; - - /** Are two intervals adjacent such as 0..41 and 42..42? */ - public adjacent(other: Interval): boolean; - - public properlyContains(other: Interval): boolean; - - /** Return the interval computed from combining this and other */ - public union(other: Interval): Interval; - - /** Return the interval in common between this and o */ - public intersection(other: Interval): Interval; - - /** - * Return the interval with elements from this not in other; - * other must not be totally enclosed (properly contained) - * within this, which would result in two disjoint intervals - * instead of the single one returned by this method. - */ - public differenceNotProperlyContained(other: Interval): Interval; - - public get length(): number; - public toString(): string; -} diff --git a/src/misc/Interval.js b/src/misc/Interval.js deleted file mode 100644 index df4fc7c..0000000 --- a/src/misc/Interval.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class Interval { - constructor(start, stop) { - this.start = start; - this.stop = stop; - } - - static of(a, b) { - // cache just a..a - if (a != b || a < 0 || a > Interval.INTERVAL_POOL_MAX_VALUE) { - return new Interval(a, b); - } - - if (Interval.cache[a] === null) { - Interval.cache[a] = new Interval(a, a); - } - - return Interval.cache[a]; - } - - equals(o) { - if (!(o instanceof Interval)) { - return false; - } - - return this.start === o.start && this.stop === o.stop; - } - - hashCode() { - let hash = 23; - hash = hash * 31 + this.start; - hash = hash * 31 + this.stop; - - return hash; - } - - startsBeforeDisjoint(other) { - return this.start < other.start && this.stop < other.start; - } - - startsBeforeNonDisjoint(other) { - return this.start <= other.start && this.stop >= other.start; - } - - startsAfter(other) { return this.start > other.start; } - - startsAfterDisjoint(other) { - return this.start > other.stop; - } - - startsAfterNonDisjoint(other) { - return this.start > other.start && this.start <= other.stop; // this.stop>=other.stop implied - } - - disjoint(other) { - return this.startsBeforeDisjoint(other) || this.startsAfterDisjoint(other); - } - - adjacent(other) { - return this.start === other.stop + 1 || this.stop === other.start - 1; - } - - properlyContains(other) { - return other.start >= this.start && other.stop <= this.stop; - } - - union(other) { - return Interval.of(Math.min(this.start, other.start), Math.max(this.stop, other.stop)); - } - - intersection(other) { - return Interval.of(Math.max(this.start, other.start), Math.min(this.stop, other.stop)); - } - - differenceNotProperlyContained(other) { - let diff = null; - - // other.start to left of this.start (or same) - if (other.startsBeforeNonDisjoint(this)) { - diff = Interval.of(Math.max(this.start, other.stop + 1), - this.stop); - } - - // other.start to right of this.start - else if (other.startsAfterNonDisjoint(this)) { - diff = Interval.of(this.start, other.start - 1); - } - - return diff; - } - - toString() { - if (this.start === this.stop) { - return this.start.toString(); - } else { - return this.start.toString() + ".." + this.stop.toString(); - } - } - - get length() { - if (this.stop < this.start) { - return 0; - } - - return this.stop - this.start + 1; - } -} - -Interval.INVALID_INTERVAL = new Interval(-1, -2); -Interval.INTERVAL_POOL_MAX_VALUE = 1000; -Interval.cache = new Array(Interval.INTERVAL_POOL_MAX_VALUE + 1); -Interval.cache.fill(null); diff --git a/src/misc/Interval.ts b/src/misc/Interval.ts new file mode 100644 index 0000000..faece50 --- /dev/null +++ b/src/misc/Interval.ts @@ -0,0 +1,160 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +// The documentation was taken from the Java implementation of the class, and is rather simple. +/* eslint-disable jsdoc/require-returns, jsdoc/require-param-description, jsdoc/require-param */ + +/** An immutable inclusive interval a..b */ +export class Interval { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static INVALID_INTERVAL = new Interval(-1, -2); + + // eslint-disable-next-line @typescript-eslint/naming-convention + public static INTERVAL_POOL_MAX_VALUE = 1000; + + private static cache: Array; + + public readonly start: number; + public readonly stop: number; + + static { + Interval.cache = new Array(Interval.INTERVAL_POOL_MAX_VALUE + 1); + Interval.cache.fill(null); + } + + public constructor(start: number, stop: number) { + this.start = start; + this.stop = stop; + } + + /** + * Creates a new interval from the given values. + * + * Interval objects are used readonly so share all with the + * same single value a==b up to some max size. Use an array as a perfect hash. + * Return shared object for 0..INTERVAL_POOL_MAX_VALUE or a new + * Interval object with a..a in it. On Java.g4, 218623 IntervalSets + * have a..a (set with 1 element). + * + * @param a The start of the interval. + * @param b The end of the interval (inclusive). + * + * @returns A cached or new interval. + */ + public static of(a: number, b: number): Interval { + // cache just a..a + if (a !== b || a < 0 || a > Interval.INTERVAL_POOL_MAX_VALUE) { + return new Interval(a, b); + } + + if (Interval.cache[a] === null) { + Interval.cache[a] = new Interval(a, a); + } + + return Interval.cache[a]!; + } + + public equals(o: unknown): boolean { + if (!(o instanceof Interval)) { + return false; + } + + return this.start === o.start && this.stop === o.stop; + } + + public hashCode(): number { + let hash = 23; + hash = hash * 31 + this.start; + hash = hash * 31 + this.stop; + + return hash; + } + + /** Does this start completely before other? Disjoint */ + public startsBeforeDisjoint(other: Interval): boolean { + return this.start < other.start && this.stop < other.start; + } + + /** Does this start at or before other? Nondisjoint */ + public startsBeforeNonDisjoint(other: Interval): boolean { + return this.start <= other.start && this.stop >= other.start; + } + + /** Does this.start start after other.stop? May or may not be disjoint */ + public startsAfter(other: Interval): boolean { + return this.start > other.start; + } + + /** Does this start completely after other? Disjoint */ + public startsAfterDisjoint(other: Interval): boolean { + return this.start > other.stop; + } + + /** Does this start after other? NonDisjoint */ + public startsAfterNonDisjoint(other: Interval): boolean { + return this.start > other.start && this.start <= other.stop; // this.stop >= other.stop implied + } + + /** Are both ranges disjoint? I.e., no overlap? */ + public disjoint(other: Interval): boolean { + return this.startsBeforeDisjoint(other) || this.startsAfterDisjoint(other); + } + + /** Are two intervals adjacent such as 0..41 and 42..42? */ + public adjacent(other: Interval): boolean { + return this.start === other.stop + 1 || this.stop === other.start - 1; + } + + public properlyContains(other: Interval): boolean { + return other.start >= this.start && other.stop <= this.stop; + } + + /** Return the interval computed from combining this and other */ + public union(other: Interval): Interval { + return Interval.of(Math.min(this.start, other.start), Math.max(this.stop, other.stop)); + } + + /** Return the interval in common between this and o */ + public intersection(other: Interval): Interval { + return Interval.of(Math.max(this.start, other.start), Math.min(this.stop, other.stop)); + } + + /** + * Return the interval with elements from this not in other; + * other must not be totally enclosed (properly contained) + * within this, which would result in two disjoint intervals + * instead of the single one returned by this method. + */ + public differenceNotProperlyContained(other: Interval): Interval | null { + let diff = null; + + if (other.startsBeforeNonDisjoint(this)) { + // other.start to left of this.start (or same) + diff = Interval.of(Math.max(this.start, other.stop + 1), this.stop); + } else if (other.startsAfterNonDisjoint(this)) { + // other.start to right of this.start + diff = Interval.of(this.start, other.start - 1); + } + + return diff; + } + + public toString(): string { + if (this.start === this.stop) { + return this.start.toString(); + } else { + return this.start.toString() + ".." + this.stop.toString(); + } + } + + public get length(): number { + if (this.stop < this.start) { + return 0; + } + + return this.stop - this.start + 1; + } +} diff --git a/src/misc/IntervalSet.d.ts b/src/misc/IntervalSet.d.ts deleted file mode 100644 index 5d804b9..0000000 --- a/src/misc/IntervalSet.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Vocabulary } from "../Vocabulary.js"; -import { Interval } from "./Interval.js"; - -export declare class IntervalSet { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static readonly COMPLETE_CHAR_SET: IntervalSet; - - // eslint-disable-next-line @typescript-eslint/naming-convention - public static readonly EMPTY_SET: IntervalSet; - - public get isNil(): boolean; - public get length(): number; - - public constructor(set?: IntervalSet); - - public static of(a: number, b: number): IntervalSet; - - public get minElement(): number; - public get maxElement(): number; - - public addOne(v: number): void; - public addRange(l: number, h: number): void; - public addInterval(other: Interval): void; - public addSet(other: IntervalSet): void; - - public complement(minElement: number, maxElement: number): IntervalSet; - public complement(vocabulary: IntervalSet): IntervalSet; - - public or(sets: IntervalSet[]): IntervalSet; - public and(other: IntervalSet): IntervalSet; - public subtract(other: IntervalSet): IntervalSet; - public contains(i: number): boolean; - public removeRange(l: number, h: number): void; - public removeOne(v: number): void; - - public toString(elementsAreChar?: boolean): string; - public toString(vocabulary: Vocabulary): string; - - public elementName(literalNames?: Array, symbolicNames?: string[], index?: number): string; - public toArray(): number[]; - - public isReadOnly(): boolean; - public setReadonly(readonly: boolean): void; - - public [Symbol.iterator](): IterableIterator; -} diff --git a/src/misc/IntervalSet.js b/src/misc/IntervalSet.ts similarity index 62% rename from src/misc/IntervalSet.js rename to src/misc/IntervalSet.ts index ec55e3e..d1b9881 100644 --- a/src/misc/IntervalSet.js +++ b/src/misc/IntervalSet.ts @@ -1,40 +1,67 @@ /* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ +* Copyright (c) The ANTLR Project. All rights reserved. +* Use of this file is governed by the BSD 3-clause license that +* can be found in the LICENSE.txt file in the project root. +*/ + +/* eslint-disable jsdoc/require-returns , jsdoc/require-param */ -import { Token } from '../Token.js'; +import { Token } from "../Token.js"; import { Interval } from "./Interval.js"; import { Lexer } from "../Lexer.js"; import { Vocabulary } from "../Vocabulary.js"; +/** + * This class implements the `IntSet` backed by a sorted array of + * non-overlapping intervals. It is particularly efficient for representing + * large collections of numbers, where the majority of elements appear as part + * of a sequential range of numbers that are all part of the set. For example, + * the set { 1, 2, 3, 4, 7, 8 } may be represented as { [1, 4], [7, 8] }. + * + * This class is able to represent sets containing any combination of values in + * the range {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE} + * (inclusive). + */ export class IntervalSet { - static COMPLETE_CHAR_SET = IntervalSet.of(Lexer.MIN_CHAR_VALUE, Lexer.MAX_CHAR_VALUE); - static EMPTY_SET = new IntervalSet(); + // eslint-disable-next-line @typescript-eslint/naming-convention + public static COMPLETE_CHAR_SET = IntervalSet.of(Lexer.MIN_CHAR_VALUE, Lexer.MAX_CHAR_VALUE); + + // eslint-disable-next-line @typescript-eslint/naming-convention + public static EMPTY_SET = new IntervalSet(); + + /** The list of sorted, disjoint intervals. */ + private intervals: Interval[] = []; + private readOnly: boolean = false; static { - IntervalSet.COMPLETE_CHAR_SET.readonly = true; - IntervalSet.EMPTY_SET.readonly = true; + IntervalSet.COMPLETE_CHAR_SET.readOnly = true; + IntervalSet.EMPTY_SET.readOnly = true; } - constructor(set) { - this.intervals = []; - this.readOnly = false; - + public constructor(set?: IntervalSet) { if (set) { this.addSet(set); } } - static of(a, b) { + /** Create a set with all ints within range [a..b] (inclusive) */ + public static of(a: number, b: number): IntervalSet { const s = new IntervalSet(); s.addRange(a, b); return s; } - get minElement() { + public [Symbol.iterator](): IterableIterator { + return this.intervals[Symbol.iterator](); + } + + /** + * Returns the minimum value contained in the set if not isNil(). + * + * @returns the minimum value contained in the set. + */ + public get minElement(): number { if (this.intervals.length === 0) { return Token.INVALID_TYPE; } @@ -42,7 +69,12 @@ export class IntervalSet { return this.intervals[0].start; } - get maxElement() { + /** + * Returns the maximum value contained in the set if not isNil(). + * + * @returns the maximum value contained in the set. + */ + public get maxElement(): number { if (this.intervals.length === 0) { return Token.INVALID_TYPE; } @@ -50,29 +82,41 @@ export class IntervalSet { return this.intervals[this.intervals.length - 1].stop; } - get isNil() { + public get isNil(): boolean { return this.intervals.length === 0; } - clear() { + public clear(): void { if (this.readOnly) { - throw new IllegalStateException("can't alter readonly IntervalSet"); + throw new Error("can't alter readonly IntervalSet"); } this.intervals = []; } - addOne(v) { + /** + * Add a single element to the set. An isolated element is stored + * as a range el..el. + */ + public addOne(v: number): void { this.addInterval(new Interval(v, v)); } - addRange(l, h) { + /** + * Add interval; i.e., add all integers from a to b to set. + * If b < a, do nothing. + * Keep list in sorted order (by left range value). + * If overlap, combine ranges. For example, + * If this is {1..5, 10..20}, adding 6..7 yields + * {1..5, 6..7, 10..20}. Adding 4..8 yields {1..8, 10..20}. + */ + public addRange(l: number, h: number): void { this.addInterval(new Interval(l, h)); } - addInterval(addition) { + public addInterval(addition: Interval): void { if (this.readOnly) { - throw new IllegalStateException("can't alter readonly IntervalSet"); + throw new Error("can't alter readonly IntervalSet"); } if (this.intervals.length === 0) { @@ -122,13 +166,15 @@ export class IntervalSet { } } - addSet(other) { - other.intervals.forEach(toAdd => this.addInterval(toAdd), this); + public addSet(other: IntervalSet): this { + other.intervals.forEach((toAdd) => { return this.addInterval(toAdd); }, this); return this; } - complement(vocabularyOrMinElement, maxElement) { + public complement(vocabulary?: IntervalSet): IntervalSet; + public complement(minElement: number, maxElement: number): IntervalSet; + public complement(vocabularyOrMinElement?: IntervalSet | number, maxElement?: number): IntervalSet { if (!vocabularyOrMinElement) { return new IntervalSet(); } @@ -141,38 +187,41 @@ export class IntervalSet { result.addSet(vocabularyOrMinElement); } else { - result.addInterval(new Interval(vocabularyOrMinElement, maxElement)); + result.addInterval(new Interval(vocabularyOrMinElement, maxElement ?? 0)); } return result.subtract(this); } - or(sets) { + /** combine all sets in the array returned the or'd value */ + public or(sets: IntervalSet[]): IntervalSet { const result = new IntervalSet(); result.addSet(this); - sets.forEach(set => result.addSet(set), this); + sets.forEach((set) => { + return result.addSet(set); + }); return result; } - and(other) { + public and(other: IntervalSet): IntervalSet { if (other.isNil) { // nothing in common with null set return new IntervalSet(); } - let myIntervals = this.intervals; - let theirIntervals = other.intervals; + const myIntervals = this.intervals; + const theirIntervals = other.intervals; let intersection; - let mySize = myIntervals.length; - let theirSize = theirIntervals.length; + const mySize = myIntervals.length; + const theirSize = theirIntervals.length; let i = 0; let j = 0; // Iterate down both interval lists looking for non-disjoint intervals. while (i < mySize && j < theirSize) { - let mine = myIntervals[i]; - let theirs = theirIntervals[j]; + const mine = myIntervals[i]; + const theirs = theirIntervals[j]; if (mine.startsBeforeDisjoint(theirs)) { // Move this iterator looking for interval that might overlap. @@ -227,7 +276,12 @@ export class IntervalSet { return intersection; } - subtract(other) { + /** + * Compute the set difference between two interval sets. The specific + * operation is {@code left - right}. If either of the input sets is + * {@code null}, it is treated as though it was an empty set. + */ + public subtract(other: IntervalSet): IntervalSet { if (this.isNil) { return new IntervalSet(); } @@ -300,110 +354,103 @@ export class IntervalSet { return result; } - contains(el) { - if (this.intervals === null) { - return false; - } else { - const n = this.intervals.length; - let l = 0; - let r = n - 1; - - // Binary search for the element in the (sorted, disjoint) array of intervals. - while (l <= r) { - const m = Math.floor((l + r) / 2); - const interval = this.intervals[m]; - if (interval.stop < el) { - l = m + 1; - } else if (interval.start > el) { - r = m - 1; - } else { - return true; - } + public contains(el: number): boolean { + const n = this.intervals.length; + let l = 0; + let r = n - 1; + + // Binary search for the element in the (sorted, disjoint) array of intervals. + while (l <= r) { + const m = Math.floor((l + r) / 2); + const interval = this.intervals[m]; + if (interval.stop < el) { + l = m + 1; + } else if (interval.start > el) { + r = m - 1; + } else { + return true; } - - return false; } + + return false; } - removeRange(toRemove) { + public removeRange(toRemove: Interval): void { if (this.readOnly) { - throw new IllegalStateException("can't alter readonly IntervalSet"); + throw new Error("can't alter readonly IntervalSet"); } if (toRemove.start === toRemove.stop) { this.removeOne(toRemove.start); } else if (this.intervals !== null) { let pos = 0; - for (let n = 0; n < this.intervals.length; n++) { - const existing = this.intervals[pos]; + for (const existing of this.intervals) { // intervals are ordered if (toRemove.stop <= existing.start) { return; - } - // check for including range, split it - else if (toRemove.start > existing.start && toRemove.stop < existing.stop) { + } else if (toRemove.start > existing.start && toRemove.stop < existing.stop) { + // check for including range, split it this.intervals[pos] = new Interval(existing.start, toRemove.start); const x = new Interval(toRemove.stop, existing.stop); this.intervals.splice(pos, 0, x); + return; - } - // check for included range, remove it - else if (toRemove.start <= existing.start && toRemove.stop >= existing.stop) { + } else if (toRemove.start <= existing.start && toRemove.stop >= existing.stop) { + // check for included range, remove it this.intervals.splice(pos, 1); pos = pos - 1; // need another pass - } - // check for lower boundary - else if (toRemove.start < existing.stop) { + } else if (toRemove.start < existing.stop) { + // check for lower boundary this.intervals[pos] = new Interval(existing.start, toRemove.start); - } - // check for upper boundary - else if (toRemove.stop < existing.stop) { + } else if (toRemove.stop < existing.stop) { + // check for upper boundary this.intervals[pos] = new Interval(toRemove.stop, existing.stop); } + pos += 1; } } } - removeOne(value) { + public removeOne(value: number): void { if (this.readOnly) { - throw new IllegalStateException("can't alter readonly IntervalSet"); + throw new Error("can't alter readonly IntervalSet"); } - if (this.intervals !== null) { - for (let i = 0; i < this.intervals.length; i++) { - const existing = this.intervals[i]; - // intervals are ordered - if (value < existing.start) { - return; - } + for (let i = 0; i < this.intervals.length; i++) { + const existing = this.intervals[i]; + // intervals are ordered + if (value < existing.start) { + return; + } else if (value === existing.start && value === existing.stop) { // check for single value range - else if (value === existing.start && value === existing.stop) { - this.intervals.splice(i, 1); - return; - } + this.intervals.splice(i, 1); + + return; + } else if (value === existing.start) { // check for lower boundary - else if (value === existing.start) { - this.intervals[i] = new Interval(existing.start + 1, existing.stop); - return; - } + this.intervals[i] = new Interval(existing.start + 1, existing.stop); + + return; + } else if (value === existing.stop) { // check for upper boundary - else if (value === existing.stop) { - this.intervals[i] = new Interval(existing.start, existing.stop); - return; - } + this.intervals[i] = new Interval(existing.start, existing.stop); + + return; + } else if (value < existing.stop) { // split existing range - else if (value < existing.stop) { - const replace = new Interval(existing.start, value); - existing.start = value + 1; - this.intervals.splice(i, 0, replace); - return; - } + const replace = new Interval(existing.start, value); + this.intervals[i] = new Interval(value + 1, existing.stop); + this.intervals.splice(i, 0, replace); + + return; } } } - toString(elementsAreCharOrVocabulary) { + public toString(elementsAreChar?: boolean): string; + public toString(vocabulary: Vocabulary): string; + public toString(elementsAreCharOrVocabulary?: boolean | Vocabulary): string { if (this.intervals.length === 0) { return "{}"; } @@ -413,11 +460,12 @@ export class IntervalSet { result += "{"; } - let elementsAreChar; + let elementsAreChar = false; let vocabulary; if (elementsAreCharOrVocabulary instanceof Vocabulary) { vocabulary = elementsAreCharOrVocabulary; - elementsAreChar = false; + } else if (Array.isArray(elementsAreCharOrVocabulary)) { + vocabulary = Vocabulary.fromTokenNames(elementsAreCharOrVocabulary); } else { elementsAreChar = elementsAreCharOrVocabulary ?? false; } @@ -428,7 +476,7 @@ export class IntervalSet { const start = interval.start; const stop = interval.stop; if (start === stop) { - if (start == Token.EOF) { + if (start === Token.EOF) { result += ""; } else if (elementsAreChar) { result += "'" + String.fromCodePoint(start) + "'"; @@ -466,34 +514,21 @@ export class IntervalSet { return result; } - elementName(vocabulary, token) { - if (token === Token.EOF) { - return ""; - } else if (token === Token.EPSILON) { - return ""; - } else { - return vocabulary.getDisplayName(token); - } - } - - toArray() { + public toArray(): number[] { const data = []; - if (this.intervals !== null) { - for (let i = 0; i < this.intervals.length; i++) { - const existing = this.intervals[i]; - for (let j = existing.start; j <= existing.stop; j++) { - data.push(j); - } + for (const interval of this.intervals) { + for (let j = interval.start; j <= interval.stop; j++) { + data.push(j); } } return data; } - get length() { + public get length(): number { let result = 0; - let intervalCount = this.intervals.length; - if (intervalCount == 1) { + const intervalCount = this.intervals.length; + if (intervalCount === 1) { const firstInterval = this.intervals[0]; return firstInterval.stop - firstInterval.start + 1; @@ -506,19 +541,26 @@ export class IntervalSet { return result; } - isReadonly() { + public isReadonly(): boolean { return this.readOnly; } - setReadonly(readonly) { + public setReadonly(readonly: boolean): void { if (this.readOnly && !readonly) { - throw new IllegalStateException("can't alter readonly IntervalSet"); + throw new Error("can't alter readonly IntervalSet"); } this.readOnly = readonly; } - [Symbol.iterator]() { - return this.intervals[Symbol.iterator](); + protected elementName(vocabulary: Vocabulary, token: number): string | null { + if (token === Token.EOF) { + return ""; + } else if (token === Token.EPSILON) { + return ""; + } else { + return vocabulary.getDisplayName(token); + } } + } diff --git a/src/misc/ParseCancellationException.ts b/src/misc/ParseCancellationException.ts new file mode 100644 index 0000000..7325e80 --- /dev/null +++ b/src/misc/ParseCancellationException.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/** + * This exception is thrown to cancel a parsing operation. This exception does + * not extend {@link RecognitionException}, allowing it to bypass the standard + * error recovery mechanisms. {@link BailErrorStrategy} throws this exception in + * response to a parse error. + * + * @author Sam Harwell + */ +export class ParseCancellationException extends Error { + + public constructor(_e: Error) { + super(); + Error.captureStackTrace(this, ParseCancellationException); + } +} diff --git a/src/misc/index.d.ts b/src/misc/index.d.ts deleted file mode 100644 index e3bff4c..0000000 --- a/src/misc/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export * from "./Interval.js"; -export * from "./IntervalSet.js"; diff --git a/src/misc/index.js b/src/misc/index.js deleted file mode 100644 index e0350a5..0000000 --- a/src/misc/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export { Interval } from './Interval.js'; -export { IntervalSet } from './IntervalSet.js'; diff --git a/src/tree/AbstractParseTreeVisitor.ts b/src/tree/AbstractParseTreeVisitor.ts new file mode 100644 index 0000000..cfcd9ed --- /dev/null +++ b/src/tree/AbstractParseTreeVisitor.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { RuleContext } from "../RuleContext.js"; +import { ErrorNode } from "./ErrorNode.js"; +import { ParseTree } from "./ParseTree.js"; +import { ParseTreeVisitor } from "./ParseTreeVisitor.js"; +import { TerminalNode } from "./TerminalNode.js"; + +export abstract class AbstractParseTreeVisitor implements ParseTreeVisitor { + public visit(tree: ParseTree): T | null { + return tree.accept(this); + } + + public visitChildren(node: RuleContext): T | null { + let result = this.defaultResult(); + const n = node.getChildCount(); + for (let i = 0; i < n; i++) { + if (!this.shouldVisitNextChild(node, result)) { + break; + } + + const c = node.getChild(i); + if (c) { + const childResult = c.accept(this); + result = this.aggregateResult(result, childResult); + } + } + + return result; + }; + + public visitTerminal(_node: TerminalNode): T | null { + return this.defaultResult(); + } + + public visitErrorNode(_node: ErrorNode): T | null { + return this.defaultResult(); + } + + protected defaultResult(): T | null { + return null; + } + + protected shouldVisitNextChild(_node: RuleContext, _currentResult: T | null): boolean { + return true; + } + + protected aggregateResult(aggregate: T | null, nextResult: T | null): T | null { + return nextResult; + } +} diff --git a/src/tree/ErrorNode.d.ts b/src/tree/ErrorNode.d.ts deleted file mode 100644 index 8ff383b..0000000 --- a/src/tree/ErrorNode.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { TerminalNode } from "./TerminalNode.js"; - -export declare class ErrorNode extends TerminalNode { - -} diff --git a/src/tree/ErrorNode.js b/src/tree/ErrorNode.js deleted file mode 100644 index af692dd..0000000 --- a/src/tree/ErrorNode.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { TerminalNode } from "./TerminalNode.js"; - -export class ErrorNode extends TerminalNode { -} diff --git a/src/tree/ErrorNodeImpl.js b/src/tree/ErrorNode.ts similarity index 70% rename from src/tree/ErrorNodeImpl.js rename to src/tree/ErrorNode.ts index d84206b..66fce09 100644 --- a/src/tree/ErrorNodeImpl.js +++ b/src/tree/ErrorNode.ts @@ -4,6 +4,9 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTreeVisitor } from "./ParseTreeVisitor.js"; +import { TerminalNode } from "./TerminalNode.js"; + /** * Represents a token that was consumed during resynchronization * rather than during a valid match operation. For example, @@ -11,18 +14,8 @@ * and deletion as well as during "consume until error recovery set" * upon no viable alternative exceptions. */ -import { TerminalNodeImpl } from "./TerminalNodeImpl.js"; - -export class ErrorNodeImpl extends TerminalNodeImpl { - constructor(token) { - super(token); - } - - isErrorNode() { - return true; - } - - accept(visitor) { +export class ErrorNode extends TerminalNode { + public override accept(visitor: ParseTreeVisitor): T | null { return visitor.visitErrorNode(this); } } diff --git a/src/tree/ParseTree.js b/src/tree/ParseTree.js deleted file mode 100644 index 30e66aa..0000000 --- a/src/tree/ParseTree.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class ParseTree { -} diff --git a/src/tree/ParseTree.d.ts b/src/tree/ParseTree.ts similarity index 85% rename from src/tree/ParseTree.d.ts rename to src/tree/ParseTree.ts index f345716..f067845 100644 --- a/src/tree/ParseTree.d.ts +++ b/src/tree/ParseTree.ts @@ -4,10 +4,9 @@ * can be found in the LICENSE.txt file in the project root. */ -import { RuleContext } from "../atn/RuleContext.js"; import { Interval } from "../misc/Interval.js"; -import { Token } from "../Token.js"; -import { TokenStream } from "../TokenStream.js"; +import { Parser } from "../Parser.js"; +import { ParseTreeVisitor } from "./ParseTreeVisitor.js"; /** * The basic notion of a tree has a parent, a payload, and a list of children. @@ -16,12 +15,12 @@ import { TokenStream } from "../TokenStream.js"; * Note: this interface is a combination of 3 Java interfaces: ParseTree, SyntaxTree and Tree. */ // eslint-disable-next-line @typescript-eslint/naming-convention -export declare interface ParseTree { +export interface ParseTree { /** * The parent of this node. If the return value is null, then this * node is the root of the tree. */ - get parent(): ParseTree | null; + parent: ParseTree | null; /** * This method returns whatever object represents the data at this node. For @@ -30,10 +29,13 @@ export declare interface ParseTree { * invocation. For abstract syntax trees (ASTs), this is a {@link Token} * object. */ - getPayload(): T; + getPayload(): unknown; /** If there are children, get the {@code i}th value indexed from 0. */ - getChild(i: number): T | null; + getChild(i: number): ParseTree | null; + + /** The {@link ParseTreeVisitor} needs a double dispatch method. */ + accept(visitor: ParseTreeVisitor): T | null; /** * How many children are there? If there is none, then this @@ -52,7 +54,7 @@ export declare interface ParseTree { * Print out a whole tree, not just a node, in LISP format * {@code (root child1 .. childN)}. Print just a node if this is a leaf. */ - toStringTree(): string; + toStringTree(ruleNames: string[], recog: Parser): string; /** * Return an {@link Interval} indicating the index in the diff --git a/src/tree/ParseTreeListener.js b/src/tree/ParseTreeListener.js deleted file mode 100644 index 0002812..0000000 --- a/src/tree/ParseTreeListener.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class ParseTreeListener { - visitTerminal(node) { - } - - visitErrorNode(node) { - } - - enterEveryRule(node) { - } - - exitEveryRule(node) { - } -} diff --git a/src/tree/ParseTreeListener.d.ts b/src/tree/ParseTreeListener.ts similarity index 56% rename from src/tree/ParseTreeListener.d.ts rename to src/tree/ParseTreeListener.ts index 76cc5b4..6493d3d 100644 --- a/src/tree/ParseTreeListener.d.ts +++ b/src/tree/ParseTreeListener.ts @@ -8,9 +8,9 @@ import { ParserRuleContext } from "../ParserRuleContext.js"; import { ErrorNode } from "./ErrorNode.js"; import { TerminalNode } from "./TerminalNode.js"; -export declare abstract class ParseTreeListener { - public visitTerminal(node: TerminalNode): void; - public visitErrorNode(node: ErrorNode): void; - public enterEveryRule(ctx: ParserRuleContext): void; - public exitEveryRule(ctx: ParserRuleContext): void; +export interface ParseTreeListener { + visitTerminal(node: TerminalNode): void; + visitErrorNode(node: ErrorNode): void; + enterEveryRule(node: ParserRuleContext): void; + exitEveryRule(node: ParserRuleContext): void; } diff --git a/src/tree/ParseTreeVisitor.d.ts b/src/tree/ParseTreeVisitor.d.ts deleted file mode 100644 index 1a10aaf..0000000 --- a/src/tree/ParseTreeVisitor.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ErrorNode } from "./ErrorNode.js"; -import { TerminalNode } from "./TerminalNode.js"; -import { ParseTree } from "./ParseTree.js"; - -export declare class ParseTreeVisitor { - public visit(tree: ParseTree): Result; - public visitChildren(node: ParseTree): Result; - public visitTerminal(node: TerminalNode): Result; - public visitErrorNode(node: ErrorNode): Result; - - protected defaultResult(): Result; - protected shouldVisitNextChild(node: ParseTree, currentResult: Result): boolean; - protected aggregateResult(aggregate: Result, nextResult: Result): Result; -} diff --git a/src/tree/ParseTreeVisitor.js b/src/tree/ParseTreeVisitor.js deleted file mode 100644 index cde4b76..0000000 --- a/src/tree/ParseTreeVisitor.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class ParseTreeVisitor { - visit(tree) { - return tree.accept(this); - } - - visitChildren(node) { - let result = this.defaultResult(); - const n = node.getChildCount(); - for (let i = 0; i < n; i++) { - if (!this.shouldVisitNextChild(node, result)) { - break; - } - - const c = node.getChild(i); - const childResult = c.accept(this); - result = this.aggregateResult(result, childResult); - } - - return result; - } - - visitTerminal(node) { - return this.defaultResult(); - } - - visitErrorNode(node) { - return this.defaultResult(); - } - - defaultResult() { - return null; - } - - shouldVisitNextChild(node, currentResult) { - return true; - } - - aggregateResult(aggregate, nextResult) { - return nextResult; - } -} diff --git a/src/tree/ParseTreeVisitor.ts b/src/tree/ParseTreeVisitor.ts new file mode 100644 index 0000000..8021b7a --- /dev/null +++ b/src/tree/ParseTreeVisitor.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { ErrorNode } from "./ErrorNode.js"; +import { TerminalNode } from "./TerminalNode.js"; +import { ParseTree } from "./ParseTree.js"; +import { RuleContext } from "../RuleContext.js"; + +/** + * This interface defines the basic notion of a parse tree visitor. Generated + * visitors implement this interface and the {@code XVisitor} interface for + * grammar {@code X}. + * + * @param T The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +export interface ParseTreeVisitor { + + /** + * Visit a parse tree, and return a user-defined result of the operation. + * + * @param tree The {@link ParseTree} to visit. + * @returns The result of visiting the parse tree. + */ + visit(tree: ParseTree): T | null; + + /** + * Visit the children of a node, and return a user-defined result of the + * operation. + * + * @param node The {@link RuleNode} whose children should be visited. + * @returns The result of visiting the children of the node. + */ + visitChildren(node: RuleContext): T | null; + + /** + * Visit a terminal node, and return a user-defined result of the operation. + * + * @param node The {@link TerminalNode} to visit. + * @returns The result of visiting the node. + */ + visitTerminal(node: TerminalNode): T | null; + + /** + * Visit an error node, and return a user-defined result of the operation. + * + * @param node The {@link ErrorNode} to visit. + * @returns The result of visiting the node. + */ + visitErrorNode(node: ErrorNode): T | null; + +} diff --git a/src/tree/ParseTreeWalker.d.ts b/src/tree/ParseTreeWalker.d.ts deleted file mode 100644 index 06e0779..0000000 --- a/src/tree/ParseTreeWalker.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTreeListener } from "./ParseTreeListener.js"; -import { ParseTree } from "./ParseTree.js"; - -export declare class ParseTreeWalker { - // eslint-disable-next-line @typescript-eslint/naming-convention - public static DEFAULT: ParseTreeWalker; - - public walk(listener: T, t: ParseTree): void; -} diff --git a/src/tree/ParseTreeWalker.js b/src/tree/ParseTreeWalker.ts similarity index 65% rename from src/tree/ParseTreeWalker.js rename to src/tree/ParseTreeWalker.ts index acfafa3..2d7451c 100644 --- a/src/tree/ParseTreeWalker.js +++ b/src/tree/ParseTreeWalker.ts @@ -4,43 +4,51 @@ * can be found in the LICENSE.txt file in the project root. */ -import { TerminalNode } from "./TerminalNode.js"; +import { ParserRuleContext } from "../ParserRuleContext.js"; +import { RuleContext } from "../RuleContext.js"; import { ErrorNode } from "./ErrorNode.js"; +import { ParseTree } from "./ParseTree.js"; +import { ParseTreeListener } from "./ParseTreeListener.js"; +import { TerminalNode } from "./TerminalNode.js"; export class ParseTreeWalker { + // eslint-disable-next-line @typescript-eslint/naming-convention + public static DEFAULT = new ParseTreeWalker(); + /** * Performs a walk on the given parse tree starting at the root and going down recursively * with depth-first search. On each node, {@link ParseTreeWalker//enterRule} is called before * recursively walking down into child nodes, then * {@link ParseTreeWalker//exitRule} is called after the recursive call to wind up. + * * @param listener The listener used by the walker to process grammar rules * @param t The parse tree to be walked on */ - walk(listener, t) { - const errorNode = t instanceof ErrorNode || - (t.isErrorNode !== undefined && t.isErrorNode()); + public walk(listener: T, t: ParseTree): void { + const errorNode = t instanceof ErrorNode; if (errorNode) { listener.visitErrorNode(t); } else if (t instanceof TerminalNode) { listener.visitTerminal(t); } else { - this.enterRule(listener, t); + const r = t as RuleContext; + this.enterRule(listener, r); for (let i = 0; i < t.getChildCount(); i++) { - const child = t.getChild(i); - this.walk(listener, child); + this.walk(listener, t.getChild(i)!); } - this.exitRule(listener, t); + this.exitRule(listener, r); } } /** - * Enters a grammar rule by first triggering the generic event {@link ParseTreeListener//enterEveryRule} + * Enters a grammar rule by first triggering the generic event {@link ParseTreeListener.enterEveryRule} * then by triggering the event specific to the given parse tree node + * * @param listener The listener responding to the trigger events * @param r The grammar rule containing the rule context */ - enterRule(listener, r) { - const ctx = r.ruleContext; + protected enterRule(listener: ParseTreeListener, r: RuleContext): void { + const ctx = r.ruleContext as ParserRuleContext; listener.enterEveryRule(ctx); ctx.enterRule(listener); } @@ -48,14 +56,13 @@ export class ParseTreeWalker { /** * Exits a grammar rule by first triggering the event specific to the given parse tree node * then by triggering the generic event {@link ParseTreeListener//exitEveryRule} + * * @param listener The listener responding to the trigger events * @param r The grammar rule containing the rule context */ - exitRule(listener, r) { - const ctx = r.ruleContext; + protected exitRule(listener: ParseTreeListener, r: RuleContext): void { + const ctx = r.ruleContext as ParserRuleContext; ctx.exitRule(listener); listener.exitEveryRule(ctx); } } - -ParseTreeWalker.DEFAULT = new ParseTreeWalker(); diff --git a/src/tree/TerminalNode.d.ts b/src/tree/TerminalNode.d.ts deleted file mode 100644 index 9874e19..0000000 --- a/src/tree/TerminalNode.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "./ParseTree.js"; -import { Token } from "../Token.js"; -import { Interval } from "../misc/Interval.js"; - -export declare class TerminalNode implements ParseTree { - public symbol: Token; - public get parent(): ParseTree | null; - - public constructor(symbol: Token); - - public getParent(): ParseTree | null; - public getChild(i: number): ParseTree | null; - public getSymbol(): Token; - public getPayload(): T; - public getSourceInterval(): Interval; - public getChildCount(): number; - - public toStringTree(): string; - - public getText(): string; -} diff --git a/src/tree/TerminalNode.js b/src/tree/TerminalNode.js deleted file mode 100644 index d40d860..0000000 --- a/src/tree/TerminalNode.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "./ParseTree.js"; - -export class TerminalNode extends ParseTree { -} diff --git a/src/tree/TerminalNode.ts b/src/tree/TerminalNode.ts new file mode 100644 index 0000000..8130318 --- /dev/null +++ b/src/tree/TerminalNode.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Interval } from "../misc/Interval.js"; +import { Token } from "../Token.js"; +import { ParseTree } from "./ParseTree.js"; +import { ParseTreeVisitor } from "./ParseTreeVisitor.js"; + +export class TerminalNode implements ParseTree { + public parent: ParseTree | null = null; + public symbol: Token; + + public constructor(symbol: Token) { + this.symbol = symbol; + } + + public getChild(_i: number): ParseTree | null { + return null; + } + + public getSymbol(): Token { + return this.symbol; + } + + public getPayload(): Token | null { + return this.symbol; + } + + public getSourceInterval(): Interval { + if (this.symbol === null) { + return Interval.INVALID_INTERVAL; + } + const tokenIndex = this.symbol.tokenIndex; + + return new Interval(tokenIndex, tokenIndex); + } + + public getChildCount(): number { + return 0; + } + + public accept(visitor: ParseTreeVisitor): T | null { + return visitor.visitTerminal(this); + } + + public getText(): string { + return this.symbol?.text ?? ""; + } + + public toString(): string { + if (this.symbol?.type === Token.EOF) { + return ""; + } else { + return this.symbol?.text ?? ""; + } + } + + public toStringTree(): string { + return this.toString(); + } +} diff --git a/src/tree/TerminalNodeImpl.js b/src/tree/TerminalNodeImpl.js deleted file mode 100644 index 83a8b5a..0000000 --- a/src/tree/TerminalNodeImpl.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Interval } from "../misc/Interval.js"; -import { Token } from '../Token.js'; -import { TerminalNode } from "./TerminalNode.js"; - -export class TerminalNodeImpl extends TerminalNode { - constructor(symbol) { - super(); - this._parent = null; - this.symbol = symbol; - } - - getChild(i) { - return null; - } - - getSymbol() { - return this.symbol; - } - - get parent() { - return this._parent; - } - - set parent(parent) { - this._parent = parent; - } - - getPayload() { - return this.symbol; - } - - getSourceInterval() { - if (this.symbol === null) { - return Interval.INVALID_INTERVAL; - } - const tokenIndex = this.symbol.tokenIndex; - return new Interval(tokenIndex, tokenIndex); - } - - getChildCount() { - return 0; - } - - accept(visitor) { - return visitor.visitTerminal(this); - } - - getText() { - return this.symbol.text; - } - - toString() { - if (this.symbol.type === Token.EOF) { - return ""; - } else { - return this.symbol.text; - } - } - - toStringTree() { - return this.toString(); - } -} diff --git a/src/tree/Trees.d.ts b/src/tree/Trees.d.ts deleted file mode 100644 index 295c467..0000000 --- a/src/tree/Trees.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "./ParseTree.js"; -import { Parser } from "../Parser.js"; - -/** A set of utility routines useful for all kinds of ANTLR trees. */ -export declare class Trees { - /** - * Print out a whole tree in LISP form. {@link //getNodeText} is used on the - * node payloads to get the text for the nodes. Detect - * parse trees and extract data appropriately. - */ - public static toStringTree(tree: ParseTree, ruleNames: string[] | null, recog: Parser | null): string; - - public static getNodeText(t: ParseTree, ruleNames: string[] | null, recog: Parser | null): string; - - /** - * Return ordered list of all children of this node - */ - public static getChildren(t: ParseTree): ParseTree[]; - - /** - * Return a list of all ancestors of this node. The first node of - * list is the root and the last is the parent of this node. - */ - public static getAncestors(t: ParseTree): ParseTree[]; - - public static findAllTokenNodes(t: ParseTree, ttype: number): ParseTree[]; - - public static findAllRuleNodes(t: ParseTree, ruleIndex: number): ParseTree[]; - - public static findAllNodes(t: ParseTree, index: number, findTokens: boolean): ParseTree[]; - - public static descendants(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/Trees.js b/src/tree/Trees.ts similarity index 53% rename from src/tree/Trees.js rename to src/tree/Trees.ts index 827bf43..76bf8ef 100644 --- a/src/tree/Trees.js +++ b/src/tree/Trees.ts @@ -4,47 +4,55 @@ * can be found in the LICENSE.txt file in the project root. */ -import { Token } from '../Token.js'; -import { ErrorNode } from './ErrorNode.js'; -import { TerminalNode } from './TerminalNode.js'; -import { escapeWhitespace } from "../utils/escapeWhitespace.js"; -import { RuleContext } from "../atn/RuleContext.js"; +/* eslint-disable jsdoc/require-returns, jsdoc/require-param */ + +import { isToken } from "../Token.js"; +import { RuleContext } from "../RuleContext.js"; + +import { escapeWhitespace } from "../utils/helpers.js"; +import { ErrorNode } from "./ErrorNode.js"; +import { TerminalNode } from "./TerminalNode.js"; +import { ParseTree } from "./ParseTree.js"; +import { Parser } from "../Parser.js"; /** A set of utility routines useful for all kinds of ANTLR trees. */ -export const Trees = { +export class Trees { /** - * Print out a whole tree in LISP form. {@link //getNodeText} is used on the + * Print out a whole tree in LISP form. {@link getNodeText} is used on the * node payloads to get the text for the nodes. Detect * parse trees and extract data appropriately. */ - toStringTree: function (tree, ruleNames, recog) { - ruleNames = ruleNames || null; - recog = recog || null; + public static toStringTree(tree: ParseTree, ruleNames: string[] | null, recog?: Parser | null): string { + ruleNames = ruleNames ?? null; + recog = recog ?? null; if (recog !== null) { ruleNames = recog.ruleNames; } + let s = Trees.getNodeText(tree, ruleNames); - s = escapeWhitespace(s, false); + s = escapeWhitespace(s!, false); const c = tree.getChildCount(); if (c === 0) { return s; } - let res = "(" + s + ' '; + + let res = "(" + s + " "; if (c > 0) { - s = Trees.toStringTree(tree.getChild(0), ruleNames); + s = Trees.toStringTree(tree.getChild(0)!, ruleNames); res = res.concat(s); } for (let i = 1; i < c; i++) { - s = Trees.toStringTree(tree.getChild(i), ruleNames); - res = res.concat(' ' + s); + s = Trees.toStringTree(tree.getChild(i)!, ruleNames); + res = res.concat(" " + s); } res = res.concat(")"); + return res; - }, + } - getNodeText: function (t, ruleNames, recog) { - ruleNames = ruleNames || null; - recog = recog || null; + public static getNodeText(t: ParseTree, ruleNames: string[] | null, recog?: Parser | null): string | null { + ruleNames = ruleNames ?? null; + recog = recog ?? null; if (recog !== null) { ruleNames = recog.ruleNames; } @@ -53,9 +61,10 @@ export const Trees = { const context = t.ruleContext; const altNumber = context.getAltNumber(); // use const value of ATN.INVALID_ALT_NUMBER to avoid circular dependency - if (altNumber != 0) { + if (altNumber !== 0) { return ruleNames[t.ruleIndex] + ":" + altNumber; } + return ruleNames[t.ruleIndex]; } else if (t instanceof ErrorNode) { return t.toString(); @@ -67,55 +76,72 @@ export const Trees = { } // no recog for rule names const payload = t.getPayload(); - if (payload instanceof Token) { + if (isToken(payload)) { return payload.text; } - return t.getPayload().toString(); - }, + + return String(t.getPayload()); + } /** * Return ordered list of all children of this node */ - getChildren: function (t) { - const list = []; + public static getChildren(t: ParseTree): ParseTree[] { + const list: ParseTree[] = []; for (let i = 0; i < t.getChildCount(); i++) { - list.push(t.getChild(i)); + list.push(t.getChild(i)!); } + return list; - }, + } /** * Return a list of all ancestors of this node. The first node of * list is the root and the last is the parent of this node. */ - getAncestors: function (t) { - let ancestors = []; - t = t.getParent(); - while (t !== null) { - ancestors = [t].concat(ancestors); - t = t.getParent(); + public static getAncestors(t: ParseTree): ParseTree[] { + if (t.parent === null) { + return []; + } + + let ancestors: ParseTree[] = []; + let p: ParseTree | null = t.parent; + while (p !== null) { + ancestors = [p].concat(ancestors); + p = p.parent; } + return ancestors; - }, + } - findAllTokenNodes: function (t, ttype) { + public static findAllTokenNodes(t: ParseTree, ttype: number): ParseTree[] { return Trees.findAllNodes(t, ttype, true); - }, + } - findAllRuleNodes: function (t, ruleIndex) { + public static findAllRuleNodes(t: ParseTree, ruleIndex: number): ParseTree[] { return Trees.findAllNodes(t, ruleIndex, false); - }, + } + + public static findAllNodes(t: ParseTree, index: number, findTokens: boolean): ParseTree[] { + const nodes: ParseTree[] = []; + Trees.doFindAllNodes(t, index, findTokens, nodes); + + return nodes; + } + + public static descendants(t: ParseTree): ParseTree[] { + let nodes = [t]; + for (let i = 0; i < t.getChildCount(); i++) { + nodes = nodes.concat(Trees.descendants(t.getChild(i)!)); + } - findAllNodes: function (t, index, findTokens) { - const nodes = []; - Trees._findAllNodes(t, index, findTokens, nodes); return nodes; - }, + } - _findAllNodes: function (t, index, findTokens, nodes) { + private static doFindAllNodes(t: ParseTree, index: number, findTokens: boolean, nodes: ParseTree[]): void { // check this node (the root) first if (findTokens && (t instanceof TerminalNode)) { - if (t.symbol.type === index) { + if (t.symbol?.type === index) { nodes.push(t); } } else if (!findTokens && (t instanceof RuleContext)) { @@ -125,15 +151,8 @@ export const Trees = { } // check children for (let i = 0; i < t.getChildCount(); i++) { - Trees._findAllNodes(t.getChild(i), index, findTokens, nodes); - } - }, - - descendants: function (t) { - let nodes = [t]; - for (let i = 0; i < t.getChildCount(); i++) { - nodes = nodes.concat(Trees.descendants(t.getChild(i))); + Trees.doFindAllNodes(t.getChild(i)!, index, findTokens, nodes); } - return nodes; } + }; diff --git a/src/tree/index.js b/src/tree/index.js deleted file mode 100644 index 210e098..0000000 --- a/src/tree/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export { ErrorNode } from './ErrorNode.js'; -export { TerminalNode } from './TerminalNode.js'; -export { ParseTreeListener } from './ParseTreeListener.js'; -export { ParseTreeVisitor } from './ParseTreeVisitor.js'; -export { ParseTreeWalker } from './ParseTreeWalker.js'; -export { Trees } from './Trees.js'; - -export * from "./xpath/index.js"; diff --git a/src/tree/index.d.ts b/src/tree/index.ts similarity index 90% rename from src/tree/index.d.ts rename to src/tree/index.ts index 86cbc1e..d8c438f 100644 --- a/src/tree/index.d.ts +++ b/src/tree/index.ts @@ -9,6 +9,7 @@ export * from "./TerminalNode.js"; export * from "./ParseTree.js"; export * from "./ParseTreeListener.js"; export * from "./ParseTreeVisitor.js"; +export * from "./AbstractParseTreeVisitor.js"; export * from "./ParseTreeWalker.js"; export * from "./xpath/index.js"; diff --git a/src/tree/xpath/XPath.d.ts b/src/tree/xpath/XPath.d.ts deleted file mode 100644 index c41a922..0000000 --- a/src/tree/xpath/XPath.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Parser } from "../../Parser.js"; -import { ParseTree } from "../ParseTree.js"; -import { Token } from "../../Token.js"; -import { XPathElement } from "./XPathElement.js"; - -/** - * Represent a subset of XPath XML path syntax for use in identifying nodes in - * parse trees. - * - * Split path into words and separators `/` and `//` via ANTLR - * itself then walk path elements from left to right. At each separator-word - * pair, find set of nodes. Next stage uses those as work list. - * - * The basic interface is - * {@link XPath#findAll ParseTree.findAll}`(tree, pathString, parser)`. - * But that is just shorthand for: - * - * ``` - * let p = new XPath(parser, pathString); - * return p.evaluate(tree); - * ``` - * - * See `TestXPath` for descriptions. In short, this - * allows operators: - * - * | | | - * | --- | --- | - * | `/` | root | - * | `//` | anywhere | - * | `!` | invert; this much appear directly after root or anywhere operator | - * - * and path elements: - * - * | | | - * | --- | --- | - * | `ID` | token name | - * | `'string'` | any string literal token from the grammar | - * | `expr` | rule name | - * | `*` | wildcard matching any node | - * - * Whitespace is not allowed. - */ -export class XPath { - /* eslint-disable @typescript-eslint/naming-convention */ - - public static readonly WILDCARD: string; // word not operator/separator - public static readonly NOT: string; // word for invert operator - - /* eslint-enable @typescript-eslint/naming-convention */ - - protected path: string; - protected elements: XPathElement[]; - protected parser: Parser; - - public constructor(parser: Parser, path: string); - - public static findAll(tree: ParseTree, xpath: string, parser: Parser): Set; - - // TODO: check for invalid token/rule names, bad syntax - - public split(path: string): XPathElement[]; - - /** - * Return a list of all nodes starting at `t` as root that satisfy the - * path. The root `/` is relative to the node passed to. - */ - public evaluate(t: ParseTree): Set; - - /** - * Convert word like `*` or `ID` or `expr` to a path - * element. `anywhere` is `true` if `//` precedes the - * word. - */ - protected getXPathElement(wordToken: Token, anywhere: boolean): XPathElement; - -} diff --git a/src/tree/xpath/XPath.js b/src/tree/xpath/XPath.ts similarity index 68% rename from src/tree/xpath/XPath.js rename to src/tree/xpath/XPath.ts index b1f2151..d32904f 100644 --- a/src/tree/xpath/XPath.js +++ b/src/tree/xpath/XPath.ts @@ -4,9 +4,16 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-returns, jsdoc/require-param */ + import { CharStreams } from "../../CharStreams.js"; import { CommonTokenStream } from "../../CommonTokenStream.js"; +import { LexerNoViableAltException } from "../../LexerNoViableAltException.js"; +import { Parser } from "../../Parser.js"; +import { ParserRuleContext } from "../../ParserRuleContext.js"; +import { ParseTree } from "../ParseTree.js"; import { Token } from "../../Token.js"; +import { XPathElement } from "./XPathElement.js"; import { XPathLexer } from "./XPathLexer.js"; import { XPathLexerErrorListener } from "./XPathLexerErrorListener.js"; import { XPathRuleAnywhereElement } from "./XPathRuleAnywhereElement.js"; @@ -15,8 +22,6 @@ import { XPathTokenAnywhereElement } from "./XPathTokenAnywhereElement.js"; import { XPathTokenElement } from "./XPathTokenElement.js"; import { XPathWildcardAnywhereElement } from "./XPathWildcardAnywhereElement.js"; import { XPathWildcardElement } from "./XPathWildcardElement.js"; -import { ParserRuleContext } from "../../ParserRuleContext.js"; -import { LexerNoViableAltException } from "../../LexerNoViableAltException.js"; /** * Represent a subset of XPath XML path syntax for use in identifying nodes in @@ -56,107 +61,136 @@ import { LexerNoViableAltException } from "../../LexerNoViableAltException.js"; * Whitespace is not allowed. */ export class XPath { - static WILDCARD = "*"; // word not operator/separator - static NOT = "!"; // word for invert operator + public static readonly WILDCARD: string = "*"; // word not operator/separator + public static readonly NOT: string = "!"; // word for invert operator + + protected path: string; + protected elements: XPathElement[]; + protected parser: Parser; - constructor(parser, path) { + public constructor(parser: Parser, path: string) { this.parser = parser; this.path = path; this.elements = this.split(path); // console.log(this.elements.toString()); } - // TODO: check for invalid token/rule names, bad syntax + public static findAll(tree: ParseTree, xpath: string, parser: Parser): Set { + const p: XPath = new XPath(parser, xpath); - split(path) { - let lexer = new class extends XPathLexer { - constructor(stream) { - super(stream); - } + return p.evaluate(tree); + } + + // TODO: check for invalid token/rule names, bad syntax - recover(e) { throw e; } - }(CharStreams.fromString(path)); + public split(path: string): XPathElement[] { + const lexer = new XPathLexer(CharStreams.fromString(path)); + lexer.recover = (e: LexerNoViableAltException) => { throw e; }; lexer.removeErrorListeners(); lexer.addErrorListener(new XPathLexerErrorListener()); - let tokenStream = new CommonTokenStream(lexer); + const tokenStream = new CommonTokenStream(lexer); try { tokenStream.fill(); - } - catch (e) { + } catch (e) { if (e instanceof LexerNoViableAltException) { - let pos = lexer.column; - let msg = "Invalid tokens or characters at index " + pos + " in path '" + path + "' -- " + e.message; + const pos = lexer.column; + const msg = "Invalid tokens or characters at index " + pos + " in path '" + path + "' -- " + e.message; throw new RangeError(msg); } throw e; } - let tokens = tokenStream.getTokens(); + const tokens: Token[] = tokenStream.getTokens(); // console.log("path=" + path + "=>" + tokens); - let elements = []; - let n = tokens.length; - let i = 0; - + const elements: XPathElement[] = []; + const n: number = tokens.length; + let i: number = 0; loop: while (i < n) { - let el = tokens[i]; - let next; + const el: Token = tokens[i]; + let next: Token | undefined; switch (el.type) { case XPathLexer.ROOT: - case XPathLexer.ANYWHERE: { - const anywhere = (el.type === XPathLexer.ANYWHERE); + case XPathLexer.ANYWHERE: + const anywhere: boolean = el.type === XPathLexer.ANYWHERE; i++; next = tokens[i]; - const invert = next.type === XPathLexer.BANG; + const invert: boolean = next.type === XPathLexer.BANG; if (invert) { i++; next = tokens[i]; } - let pathElement = this.getXPathElement(next, anywhere); + const pathElement: XPathElement = this.getXPathElement(next, anywhere); pathElement.invert = invert; elements.push(pathElement); i++; break; - } case XPathLexer.TOKEN_REF: case XPathLexer.RULE_REF: - case XPathLexer.WILDCARD: { + case XPathLexer.WILDCARD: elements.push(this.getXPathElement(el, false)); i++; break; - } - case Token.EOF: { + case Token.EOF: break loop; - } - default: { + default: throw new Error("Unknown path element " + el); - } } } + return elements; } + /** + * Return a list of all nodes starting at `t` as root that satisfy the + * path. The root `/` is relative to the node passed to {@link evaluate}. + */ + public evaluate(t: ParseTree): Set { + const dummyRoot = new ParserRuleContext(); + dummyRoot.addChild(t as ParserRuleContext); + + let work = new Set([dummyRoot]); + + let i: number = 0; + while (i < this.elements.length) { + const next = new Set(); + for (const node of work) { + if (node.getChildCount() > 0) { + // only try to match next element if it has children + // e.g., //func/*/stat might have a token node for which + // we can't go looking for stat nodes. + const matching = this.elements[i].evaluate(node); + matching.forEach((tree) => { next.add(tree); }, next); + } + } + i++; + work = next; + } + + return work; + } + /** * Convert word like `*` or `ID` or `expr` to a path * element. `anywhere` is `true` if `//` precedes the * word. */ - getXPathElement(wordToken, anywhere) { + protected getXPathElement(wordToken: Token, anywhere: boolean): XPathElement { if (wordToken.type === Token.EOF) { throw new Error("Missing path element at end of path"); } - let word = wordToken.text; + const word = wordToken.text; if (word == null) { throw new Error("Expected wordToken to have text content."); } - let ttype = this.parser.getTokenType(word); - let ruleIndex = this.parser.getRuleIndex(word); + const ttype: number = this.parser.getTokenType(word); + const ruleIndex: number = this.parser.getRuleIndex(word); switch (wordToken.type) { case XPathLexer.WILDCARD: return anywhere ? @@ -169,6 +203,7 @@ export class XPath { wordToken.start + " isn't a valid token name"); } + return anywhere ? new XPathTokenAnywhereElement(word, ttype) : new XPathTokenElement(word, ttype); @@ -178,43 +213,10 @@ export class XPath { wordToken.start + " isn't a valid rule name"); } + return anywhere ? new XPathRuleAnywhereElement(word, ruleIndex) : new XPathRuleElement(word, ruleIndex); } } - - static findAll(tree, xpath, parser) { - let p = new XPath(parser, xpath); - return p.evaluate(tree); - } - - /** - * Return a list of all nodes starting at `t` as root that satisfy the - * path. The root `/` is relative to the node passed to {@link evaluate}. - */ - evaluate(t) { - let dummyRoot = new ParserRuleContext(); - dummyRoot.addChild(t); - - let work = new Set([dummyRoot]); - - let i = 0; - while (i < this.elements.length) { - let next = new Set(); - for (let node of work) { - if (node.getChildCount() > 0) { - // only try to match next element if it has children - // e.g., //func/*/stat might have a token node for which - // we can't go looking for stat nodes. - let matching = this.elements[i].evaluate(node); - matching.forEach(next.add, next); - } - } - i++; - work = next; - } - - return work; - } } diff --git a/src/tree/xpath/XPathElement.js b/src/tree/xpath/XPathElement.js deleted file mode 100644 index 0230447..0000000 --- a/src/tree/xpath/XPathElement.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export class XPathElement { - /** Construct element like `/ID` or `ID` or `/*` etc... - * op is null if just node - */ - constructor(nodeName) { - this.nodeName = nodeName; - this.invert = false; - } - - /** - * Given tree rooted at `t` return all nodes matched by this path - * element. - */ - evaluate(t) { } - - toString() { - let inv = this.invert ? "!" : ""; - let className = Object.constructor.name; - - return className + "[" + inv + this.nodeName + "]"; - } -} diff --git a/src/tree/xpath/XPathElement.d.ts b/src/tree/xpath/XPathElement.ts similarity index 59% rename from src/tree/xpath/XPathElement.d.ts rename to src/tree/xpath/XPathElement.ts index 7bebb00..36f45cb 100644 --- a/src/tree/xpath/XPathElement.d.ts +++ b/src/tree/xpath/XPathElement.ts @@ -4,20 +4,29 @@ * can be found in the LICENSE.txt file in the project root. */ +/* eslint-disable jsdoc/require-param */ + import { ParseTree } from "../ParseTree.js"; export abstract class XPathElement { public invert: boolean; - protected nodeName: string; /** * Construct element like `/ID` or `ID` or `/*` etc... - * op is null if just node + * op is null if just node */ - public constructor(nodeName: string); + public constructor(nodeName: string) { + this.nodeName = nodeName; + this.invert = false; + } + + public toString(): string { + const inv: string = this.invert ? "!" : ""; + const className: string = Object.constructor.name; - public toString(): string; + return className + "[" + inv + this.nodeName + "]"; + } /** * Given tree rooted at `t` return all nodes matched by this path diff --git a/src/tree/xpath/XPathLexer.js b/src/tree/xpath/XPathLexer.ts similarity index 91% rename from src/tree/xpath/XPathLexer.js rename to src/tree/xpath/XPathLexer.ts index be86a42..82d6418 100644 --- a/src/tree/xpath/XPathLexer.js +++ b/src/tree/xpath/XPathLexer.ts @@ -1,88 +1,88 @@ -// Generated from XPathLexer.g4 by ANTLR 4.13.0 +// Generated from src/tree/xpath/XPathLexer.g4 by ANTLR 4.13.1 +import { CharStream } from "../../CharStream.js"; import { Lexer } from "../../Lexer.js"; -import { Token } from "../../Token.js"; +import { RuleContext } from "../../RuleContext.js"; import { Vocabulary } from "../../Vocabulary.js"; +import { ATN } from "../../atn/ATN.js"; import { ATNDeserializer } from "../../atn/ATNDeserializer.js"; +import { DecisionState } from "../../atn/DecisionState.js"; import { LexerATNSimulator } from "../../atn/LexerATNSimulator.js"; import { PredictionContextCache } from "../../atn/PredictionContextCache.js"; import { DFA } from "../../dfa/DFA.js"; export class XPathLexer extends Lexer { - static TOKEN_REF = 1; - static RULE_REF = 2; - static ANYWHERE = 3; - static ROOT = 4; - static WILDCARD = 5; - static BANG = 6; - static ID = 7; - static STRING = 8; - static EOF = Token.EOF; + public static readonly TOKEN_REF = 1; + public static readonly RULE_REF = 2; + public static readonly ANYWHERE = 3; + public static readonly ROOT = 4; + public static readonly WILDCARD = 5; + public static readonly BANG = 6; + public static readonly ID = 7; + public static readonly STRING = 8; - static channelNames = [ + public static readonly channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN", ]; - static literalNames = [ + public static readonly literalNames = [ null, null, null, "'//'", "'/'", "'*'", "'!'", ]; - static symbolicNames = [ + public static readonly symbolicNames = [ null, "TOKEN_REF", "RULE_REF", "ANYWHERE", "ROOT", "WILDCARD", "BANG", "ID", "STRING", ]; - static modeNames = [ + public static readonly modeNames = [ "DEFAULT_MODE", ]; - static ruleNames = [ + public static readonly ruleNames = [ "ANYWHERE", "ROOT", "WILDCARD", "BANG", "ID", "NameChar", "NameStartChar", "STRING", ]; - constructor(input) { + public constructor(input: CharStream) { super(input); this.interpreter = new LexerATNSimulator(this, XPathLexer._ATN, XPathLexer.decisionsToDFA, new PredictionContextCache()); } - get grammarFileName() { return "XPathLexer.g4"; } + public get grammarFileName(): string { return "XPathLexer.g4"; } - get literalNames() { return XPathLexer.literalNames; } - get symbolicNames() { return XPathLexer.symbolicNames; } - get ruleNames() { return XPathLexer.ruleNames; } + public get literalNames(): Array { return XPathLexer.literalNames; } + public get symbolicNames(): Array { return XPathLexer.symbolicNames; } + public get ruleNames(): string[] { return XPathLexer.ruleNames; } - get serializedATN() { return XPathLexer._serializedATN; } + public get serializedATN(): number[] { return XPathLexer._serializedATN; } - get channelNames() { return XPathLexer.channelNames; } + public get channelNames(): string[] { return XPathLexer.channelNames; } - get modeNames() { return XPathLexer.modeNames; } + public get modeNames(): string[] { return XPathLexer.modeNames; } - // @Override - action(localctx, ruleIndex, actionIndex) { + public override action(localContext: RuleContext, ruleIndex: number, actionIndex: number): void { switch (ruleIndex) { case 4: - this.ID_action(localctx, actionIndex); + this.ID_action(localContext, actionIndex); break; } } - - ID_action(localctx, actionIndex) { + private ID_action(localContext: RuleContext, actionIndex: number): void { switch (actionIndex) { - case 0: { - let text = this.text; + case 0: + + const text = this.text; if (text.charAt(0) === text.charAt(0).toUpperCase()) { - this._type = XPathLexer.TOKEN_REF; + this.type = XPathLexer.TOKEN_REF; } else { - this._type = XPathLexer.RULE_REF; + this.type = XPathLexer.RULE_REF; } break; - } } } - static _serializedATN = [ + public static readonly _serializedATN: number[] = [ 4, 0, 8, 48, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 5, 4, 29, 8, 4, 10, 4, 12, 4, 32, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 5, 7, 42, 8, 7, @@ -349,8 +349,8 @@ export class XPathLexer extends Lexer { 16, 1, 0, 0, 0, 3, 0, 30, 43, 1, 1, 4, 0, ]; - static __ATN; - static get _ATN() { + private static __ATN: ATN; + public static get _ATN(): ATN { if (!XPathLexer.__ATN) { XPathLexer.__ATN = new ATNDeserializer().deserialize(XPathLexer._serializedATN); } @@ -358,11 +358,11 @@ export class XPathLexer extends Lexer { return XPathLexer.__ATN; } - static vocabulary = new Vocabulary(XPathLexer.literalNames, XPathLexer.symbolicNames, []); + private static readonly vocabulary = new Vocabulary(XPathLexer.literalNames, XPathLexer.symbolicNames, []); - get vocabulary() { + public override get vocabulary(): Vocabulary { return XPathLexer.vocabulary; } - static decisionsToDFA = XPathLexer._ATN.decisionToState.map((ds, index) => { return new DFA(ds, index); }); + private static readonly decisionsToDFA = XPathLexer._ATN.decisionToState.map((ds: DecisionState, index: number) => { return new DFA(ds, index); }); } diff --git a/src/tree/xpath/XPathLexerErrorListener.d.ts b/src/tree/xpath/XPathLexerErrorListener.d.ts deleted file mode 100644 index f18d8cf..0000000 --- a/src/tree/xpath/XPathLexerErrorListener.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { Recognizer } from "../../Recognizer.js"; -import { Token } from "../../Token.js"; -import { LexerATNSimulator } from "../../atn/LexerATNSimulator.js"; -import { BaseErrorListener } from "../../BaseErrorListener.js"; -import { RecognitionException } from "../../RecognitionException.js"; - -export class XPathLexerErrorListener extends BaseErrorListener { - public syntaxError( - recognizer: Recognizer, offendingSymbol: T | undefined, - line: number, charPositionInLine: number, msg: string, - e: RecognitionException | null): void; -} diff --git a/src/tree/xpath/XPathLexerErrorListener.js b/src/tree/xpath/XPathLexerErrorListener.js deleted file mode 100644 index 0834f1b..0000000 --- a/src/tree/xpath/XPathLexerErrorListener.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { BaseErrorListener } from "../../BaseErrorListener.js"; - -export class XPathLexerErrorListener extends BaseErrorListener { - syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, - e) { - // intentionally empty - } -} diff --git a/src/tree/xpath/XPathLexerErrorListener.ts b/src/tree/xpath/XPathLexerErrorListener.ts new file mode 100644 index 0000000..f8a3736 --- /dev/null +++ b/src/tree/xpath/XPathLexerErrorListener.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { Recognizer } from "../../Recognizer.js"; +import { RecognitionException } from "../../RecognitionException.js"; +import { Token } from "../../Token.js"; +import { ATNSimulator } from "../../atn/ATNSimulator.js"; +import { BaseErrorListener } from "../../BaseErrorListener.js"; + +export class XPathLexerErrorListener extends BaseErrorListener { + public override syntaxError(_recognizer: Recognizer, + _offendingSymbol: S | null, + _line: number, + _charPositionInLine: number, + _msg: string, + _e: RecognitionException | null): void { + // intentionally empty + } +} diff --git a/src/tree/xpath/XPathRuleAnywhereElement.d.ts b/src/tree/xpath/XPathRuleAnywhereElement.d.ts deleted file mode 100644 index fe62a58..0000000 --- a/src/tree/xpath/XPathRuleAnywhereElement.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -/** - * Either `ID` at start of path or `...//ID` in middle of path. - */ -export class XPathRuleAnywhereElement extends XPathElement { - protected ruleIndex: number; - - public constructor(ruleName: string, ruleIndex: number); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathRuleAnywhereElement.js b/src/tree/xpath/XPathRuleAnywhereElement.ts similarity index 74% rename from src/tree/xpath/XPathRuleAnywhereElement.js rename to src/tree/xpath/XPathRuleAnywhereElement.ts index 4188e91..12c7f8f 100644 --- a/src/tree/xpath/XPathRuleAnywhereElement.js +++ b/src/tree/xpath/XPathRuleAnywhereElement.ts @@ -4,6 +4,7 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTree } from "../ParseTree.js"; import { Trees } from "../Trees.js"; import { XPathElement } from "./XPathElement.js"; @@ -11,12 +12,14 @@ import { XPathElement } from "./XPathElement.js"; * Either `ID` at start of path or `...//ID` in middle of path. */ export class XPathRuleAnywhereElement extends XPathElement { - constructor(ruleName, ruleIndex) { + protected ruleIndex: number; + + public constructor(ruleName: string, ruleIndex: number) { super(ruleName); this.ruleIndex = ruleIndex; } - evaluate(t) { + public evaluate(t: ParseTree): ParseTree[] { return Trees.findAllRuleNodes(t, this.ruleIndex); } } diff --git a/src/tree/xpath/XPathRuleElement.d.ts b/src/tree/xpath/XPathRuleElement.d.ts deleted file mode 100644 index a4df80e..0000000 --- a/src/tree/xpath/XPathRuleElement.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -export class XPathRuleElement extends XPathElement { - protected ruleIndex: number; - - public constructor(ruleName: string, ruleIndex: number); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathRuleElement.js b/src/tree/xpath/XPathRuleElement.ts similarity index 74% rename from src/tree/xpath/XPathRuleElement.js rename to src/tree/xpath/XPathRuleElement.ts index e27c0ab..a6e2c04 100644 --- a/src/tree/xpath/XPathRuleElement.js +++ b/src/tree/xpath/XPathRuleElement.ts @@ -5,19 +5,22 @@ */ import { ParserRuleContext } from "../../ParserRuleContext.js"; +import { ParseTree } from "../ParseTree.js"; import { Trees } from "../Trees.js"; import { XPathElement } from "./XPathElement.js"; export class XPathRuleElement extends XPathElement { - constructor(ruleName, ruleIndex) { + protected ruleIndex: number; + + public constructor(ruleName: string, ruleIndex: number) { super(ruleName); this.ruleIndex = ruleIndex; } - evaluate(t) { + public evaluate(t: ParseTree): ParseTree[] { // return all children of t that match nodeName - let nodes = []; - for (let c of Trees.getChildren(t)) { + const nodes: ParseTree[] = []; + for (const c of Trees.getChildren(t)) { if (c instanceof ParserRuleContext) { if ((c.ruleIndex === this.ruleIndex && !this.invert) || (c.ruleIndex !== this.ruleIndex && this.invert)) { @@ -25,6 +28,7 @@ export class XPathRuleElement extends XPathElement { } } } + return nodes; } } diff --git a/src/tree/xpath/XPathTokenAnywhereElement.d.ts b/src/tree/xpath/XPathTokenAnywhereElement.d.ts deleted file mode 100644 index 808d43e..0000000 --- a/src/tree/xpath/XPathTokenAnywhereElement.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -export class XPathTokenAnywhereElement extends XPathElement { - protected tokenType: number; - - public constructor(tokenName: string, tokenType: number); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathTokenAnywhereElement.js b/src/tree/xpath/XPathTokenAnywhereElement.ts similarity index 71% rename from src/tree/xpath/XPathTokenAnywhereElement.js rename to src/tree/xpath/XPathTokenAnywhereElement.ts index ca15aeb..a61b1d2 100644 --- a/src/tree/xpath/XPathTokenAnywhereElement.js +++ b/src/tree/xpath/XPathTokenAnywhereElement.ts @@ -4,16 +4,19 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTree } from "../ParseTree.js"; import { Trees } from "../Trees.js"; import { XPathElement } from "./XPathElement.js"; export class XPathTokenAnywhereElement extends XPathElement { - constructor(tokenName, tokenType) { + protected tokenType: number; + + public constructor(tokenName: string, tokenType: number) { super(tokenName); this.tokenType = tokenType; } - evaluate(t) { + public evaluate(t: ParseTree): ParseTree[] { return Trees.findAllTokenNodes(t, this.tokenType); } } diff --git a/src/tree/xpath/XPathTokenElement.d.ts b/src/tree/xpath/XPathTokenElement.d.ts deleted file mode 100644 index 6703e52..0000000 --- a/src/tree/xpath/XPathTokenElement.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -export class XPathTokenElement extends XPathElement { - protected tokenType: number; - - public constructor(tokenName: string, tokenType: number); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathTokenElement.js b/src/tree/xpath/XPathTokenElement.ts similarity index 69% rename from src/tree/xpath/XPathTokenElement.js rename to src/tree/xpath/XPathTokenElement.ts index aaf4874..be6cce8 100644 --- a/src/tree/xpath/XPathTokenElement.js +++ b/src/tree/xpath/XPathTokenElement.ts @@ -4,27 +4,31 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTree } from "../ParseTree.js"; import { TerminalNode } from "../TerminalNode.js"; import { Trees } from "../Trees.js"; import { XPathElement } from "./XPathElement.js"; export class XPathTokenElement extends XPathElement { - constructor(tokenName, tokenType) { + protected tokenType: number; + + public constructor(tokenName: string, tokenType: number) { super(tokenName); this.tokenType = tokenType; } - evaluate(t) { + public evaluate(t: ParseTree): ParseTree[] { // return all children of t that match nodeName - let nodes = []; - for (let c of Trees.getChildren(t)) { - if (c instanceof TerminalNode) { + const nodes: ParseTree[] = []; + for (const c of Trees.getChildren(t)) { + if (c instanceof TerminalNode && c.symbol) { if ((c.symbol.type === this.tokenType && !this.invert) || (c.symbol.type !== this.tokenType && this.invert)) { nodes.push(c); } } } + return nodes; } } diff --git a/src/tree/xpath/XPathWildcardAnywhereElement.d.ts b/src/tree/xpath/XPathWildcardAnywhereElement.d.ts deleted file mode 100644 index 340a449..0000000 --- a/src/tree/xpath/XPathWildcardAnywhereElement.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -export class XPathWildcardAnywhereElement extends XPathElement { - public constructor(); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathWildcardAnywhereElement.js b/src/tree/xpath/XPathWildcardAnywhereElement.ts similarity index 82% rename from src/tree/xpath/XPathWildcardAnywhereElement.js rename to src/tree/xpath/XPathWildcardAnywhereElement.ts index beb84ef..aaf2383 100644 --- a/src/tree/xpath/XPathWildcardAnywhereElement.js +++ b/src/tree/xpath/XPathWildcardAnywhereElement.ts @@ -4,16 +4,17 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTree } from "../ParseTree.js"; import { Trees } from "../Trees.js"; import { XPath } from "./XPath.js"; import { XPathElement } from "./XPathElement.js"; export class XPathWildcardAnywhereElement extends XPathElement { - constructor() { + public constructor() { super(XPath.WILDCARD); } - evaluate(t) { + public evaluate(t: ParseTree): ParseTree[] { if (this.invert) { // !* is weird but valid (empty) return []; diff --git a/src/tree/xpath/XPathWildcardElement.d.ts b/src/tree/xpath/XPathWildcardElement.d.ts deleted file mode 100644 index c395e69..0000000 --- a/src/tree/xpath/XPathWildcardElement.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { ParseTree } from "../ParseTree.js"; -import { XPathElement } from "./XPathElement.js"; - -export class XPathWildcardElement extends XPathElement { - public constructor(); - - public evaluate(t: ParseTree): ParseTree[]; -} diff --git a/src/tree/xpath/XPathWildcardElement.js b/src/tree/xpath/XPathWildcardElement.ts similarity index 65% rename from src/tree/xpath/XPathWildcardElement.js rename to src/tree/xpath/XPathWildcardElement.ts index 4c67265..59041a3 100644 --- a/src/tree/xpath/XPathWildcardElement.js +++ b/src/tree/xpath/XPathWildcardElement.ts @@ -4,24 +4,26 @@ * can be found in the LICENSE.txt file in the project root. */ +import { ParseTree } from "../ParseTree.js"; +import { Trees } from "../Trees.js"; import { XPath } from "./XPath.js"; import { XPathElement } from "./XPathElement.js"; export class XPathWildcardElement extends XPathElement { - constructor() { + public constructor() { super(XPath.WILDCARD); } - evaluate(t) { - let kids = []; + public evaluate(t: ParseTree): ParseTree[] { + const kids: ParseTree[] = []; if (this.invert) { // !* is weird but valid (empty) return kids; } - - for (let i = 0; i < t.getChildCount(); i++) { - kids.push(t.getChild(i)); + for (const c of Trees.getChildren(t)) { + kids.push(c); } + return kids; } } diff --git a/src/tree/xpath/index.js b/src/tree/xpath/index.js deleted file mode 100644 index 3d8e145..0000000 --- a/src/tree/xpath/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export * from "./XPath.js"; -export * from "./XPathElement.js"; -export * from "./XPathLexerErrorListener.js"; -export * from "./XPathRuleAnywhereElement.js"; -export * from "./XPathRuleElement.js"; -export * from "./XPathTokenAnywhereElement.js"; -export * from "./XPathTokenElement.js"; -export * from "./XPathWildcardAnywhereElement.js"; -export * from "./XPathWildcardElement.js"; diff --git a/src/utils/DoubleDict.js b/src/utils/DoubleDict.js deleted file mode 100644 index 370cd4b..0000000 --- a/src/utils/DoubleDict.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { HashMap } from "../misc/HashMap.js"; - -export class DoubleDict { - - constructor(defaultMapCtor) { - this.defaultMapCtor = defaultMapCtor || HashMap; - this.cacheMap = new this.defaultMapCtor(); - } - - get(a, b) { - const d = this.cacheMap.get(a) || null; - return d === null ? null : (d.get(b) || null); - } - - set(a, b, o) { - let d = this.cacheMap.get(a) || null; - if (d === null) { - d = new this.defaultMapCtor(); - this.cacheMap.set(a, d); - } - d.set(b, o); - } - -} diff --git a/src/utils/DoubleDict.ts b/src/utils/DoubleDict.ts new file mode 100644 index 0000000..8d13d1e --- /dev/null +++ b/src/utils/DoubleDict.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +import { HashMap } from "../misc/HashMap.js"; +import { IComparable } from "./helpers.js"; + +export class DoubleDict { + private readonly cacheMap: HashMap>; + + public constructor() { + this.cacheMap = new HashMap(); + } + + public get(a: Key1, b: Key2): Value | null { + const d = this.cacheMap.get(a) ?? null; + + return d === null ? null : (d.get(b) ?? null); + } + + public set(a: Key1, b: Key2, o: Value): void { + let d = this.cacheMap.get(a) ?? null; + if (d === null) { + d = new HashMap(); + this.cacheMap.set(a, d); + } + + d.set(b, o); + } + +} diff --git a/src/utils/arrayToString.d.ts b/src/utils/arrayToString.d.ts deleted file mode 100644 index b3e1d04..0000000 --- a/src/utils/arrayToString.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare function arrayToString(value: unknown[]): string; diff --git a/src/utils/arrayToString.js b/src/utils/arrayToString.js deleted file mode 100644 index ae70ae8..0000000 --- a/src/utils/arrayToString.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { valueToString } from "./valueToString.js"; - -export function arrayToString(a) { - return Array.isArray(a) ? ("[" + a.map(valueToString).join(", ") + "]") : "null"; -} diff --git a/src/utils/equalArrays.js b/src/utils/equalArrays.js deleted file mode 100644 index 099e46d..0000000 --- a/src/utils/equalArrays.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function equalArrays(a, b) { - if (!Array.isArray(a) || !Array.isArray(b)) - return false; - if (a === b) - return true; - if (a.length !== b.length) - return false; - for (let i = 0; i < a.length; i++) { - if (a[i] === b[i]) - continue; - if (!a[i].equals || !a[i].equals(b[i])) - return false; - } - return true; -} diff --git a/src/utils/escapeWhitespace.js b/src/utils/escapeWhitespace.js deleted file mode 100644 index 7f7f71d..0000000 --- a/src/utils/escapeWhitespace.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function escapeWhitespace(s, escapeSpaces) { - s = s.replace(/\t/g, "\\t") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r"); - if (escapeSpaces) { - s = s.replace(/ /g, "\u00B7"); - } - return s; -} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 0000000..84b4f52 --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,169 @@ +/* + * Copyright (c) The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/** Expresses the Java concept of object equality (equality based on the content of two objects). */ +export interface IComparable { + equals(obj: unknown): boolean; + hashCode(): number; +} + +const isComparable = (candidate: unknown): candidate is IComparable => { + return typeof (candidate as IComparable).equals === "function"; +}; + +const valueToString = (v: null | string): string => { + return v === null ? "null" : v; +}; + +/** + * @param value The array to stringify. + * + * @returns a human readable string of an array (usually for debugging and testing). + */ +export const arrayToString = (value: unknown[] | null): string => { + return Array.isArray(value) ? ("[" + value.map(valueToString).join(", ") + "]") : "null"; +}; + +/** + * Compares two arrays for equality, using object equality for elements. + * + * @param a The first array to compare. + * @param b The second array to compare. + * + * @returns `true` if `a` and `b` are equal. + */ +export const equalArrays = (a: unknown[], b: unknown[]): boolean => { + if (a === b) { + return true; + } + + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + const left = a[i]; + const right = b[i]; + if (left === right) { + continue; + } + + if (isComparable(left) && !left.equals(right)) { + return false; + } + } + + return true; +}; + +/** + * Converts all non-visible whitespaces to escaped equivalents. + * + * @param s The string to convert. + * @param escapeSpaces A flag indicating whether to escape spaces too. + * + * @returns The converted string. + */ +export const escapeWhitespace = (s: string, escapeSpaces = false): string => { + s = s.replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + if (escapeSpaces) { + s = s.replace(/ /g, "\u00B7"); + } + + return s; +}; + +/** + * Compares two objects for equality, using object equality. + * + * @param a The first object to compare. + * @param b The second object to compare. + * + * @returns `true` if `a` and `b` are equal. + */ +export const standardEqualsFunction = (a: IComparable | null, b: unknown): boolean => { + return a ? a.equals(b) : a === b; +}; + +const stringSeedHashCode = Math.round(Math.random() * Math.pow(2, 32)); + +/** + * Generates a hash code for the given string using the Murmur3 algorithm. + * + * @param value The string to generate a hash code for. + * + * @returns The generated hash code. + */ +export const stringHashCode = (value: string): number => { + let h1b; + let k1; + + const remainder = value.length & 3; // key.length % 4 + const bytes = value.length - remainder; + let h1 = stringSeedHashCode; + const c1 = 0xcc9e2d51; + const c2 = 0x1b873593; + let i = 0; + + while (i < bytes) { + k1 = + ((value.charCodeAt(i) & 0xff)) | + ((value.charCodeAt(++i) & 0xff) << 8) | + ((value.charCodeAt(++i) & 0xff) << 16) | + ((value.charCodeAt(++i) & 0xff) << 24); + ++i; + + k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; + h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); + } + + k1 = 0; + + /* eslint-disable no-fallthrough */ + switch (remainder) { + case 3: + k1 ^= (value.charCodeAt(i + 2) & 0xff) << 16; + // no-break + case 2: + k1 ^= (value.charCodeAt(i + 1) & 0xff) << 8; + // no-break + case 1: + k1 ^= (value.charCodeAt(i) & 0xff); + k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; + h1 ^= k1; + default: + } + + h1 ^= value.length; + + h1 ^= h1 >>> 16; + h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; + h1 ^= h1 >>> 13; + h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; + h1 ^= h1 >>> 16; + + return h1 >>> 0; +}; + +/** + * Generates a hash code for the given object using either the Murmur3 algorithm (for strings) or the object's + * `hashCode` method. + * + * @param a The object to generate a hash code for. + * + * @returns The generated hash code. + */ +export const standardHashCodeFunction = (a: string | IComparable): number => { + return a ? typeof a === "string" ? stringHashCode(a) : a.hashCode() : -1; +}; diff --git a/src/utils/index.d.ts b/src/utils/index.d.ts deleted file mode 100644 index f356b3b..0000000 --- a/src/utils/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export * from "./stringToCharArray.js"; -export * from "./arrayToString.js"; diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index 3ce4d47..0000000 --- a/src/utils/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export { arrayToString } from "./arrayToString.js"; -export { stringToCharArray } from "./stringToCharArray.js"; diff --git a/src/utils/standardEqualsFunction.js b/src/utils/standardEqualsFunction.js deleted file mode 100644 index dadd87b..0000000 --- a/src/utils/standardEqualsFunction.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function standardEqualsFunction(a, b) { - return a ? a.equals(b) : a === b; -} diff --git a/src/utils/standardHashCodeFunction.js b/src/utils/standardHashCodeFunction.js deleted file mode 100644 index c2ad441..0000000 --- a/src/utils/standardHashCodeFunction.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -import { stringHashCode } from "./stringHashCode.js"; - -export function standardHashCodeFunction(a) { - return a ? typeof a === 'string' ? stringHashCode(a) : a.hashCode() : -1; -} diff --git a/src/utils/stringHashCode.js b/src/utils/stringHashCode.js deleted file mode 100644 index 3109ee8..0000000 --- a/src/utils/stringHashCode.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export const StringSeedHashCode = Math.round(Math.random() * Math.pow(2, 32)); - -export function stringHashCode(value) { - if (!value) { - return 0; - } - const type = typeof value; - const key = type === 'string' ? value : type === 'object' && value.toString ? value.toString() : false; - if (!key) { - return 0; - } - let h1b, k1; - - const remainder = key.length & 3; // key.length % 4 - const bytes = key.length - remainder; - let h1 = StringSeedHashCode; - const c1 = 0xcc9e2d51; - const c2 = 0x1b873593; - let i = 0; - - while (i < bytes) { - k1 = - ((key.charCodeAt(i) & 0xff)) | - ((key.charCodeAt(++i) & 0xff) << 8) | - ((key.charCodeAt(++i) & 0xff) << 16) | - ((key.charCodeAt(++i) & 0xff) << 24); - ++i; - - k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; - h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); - } - - k1 = 0; - - switch (remainder) { - case 3: - k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; - // no-break - case 2: - k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; - // no-break - case 1: - k1 ^= (key.charCodeAt(i) & 0xff); - k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; - h1 ^= k1; - } - - h1 ^= key.length; - - h1 ^= h1 >>> 16; - h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; - h1 ^= h1 >>> 13; - h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; -} diff --git a/src/utils/stringToCharArray.d.ts b/src/utils/stringToCharArray.d.ts deleted file mode 100644 index 3525ce2..0000000 --- a/src/utils/stringToCharArray.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export declare function stringToCharArray(str: string): Uint16Array; diff --git a/src/utils/stringToCharArray.js b/src/utils/stringToCharArray.js deleted file mode 100644 index d35d5ab..0000000 --- a/src/utils/stringToCharArray.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function stringToCharArray(str) { - let result = new Uint16Array(str.length); - for (let i = 0; i < str.length; i++) { - result[i] = str.charCodeAt(i); - } - return result; -} diff --git a/src/utils/titleCase.js b/src/utils/titleCase.js deleted file mode 100644 index 5e3d0dd..0000000 --- a/src/utils/titleCase.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function titleCase(str) { - return str.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1); - }); -} diff --git a/src/utils/valueToString.js b/src/utils/valueToString.js deleted file mode 100644 index 24804cd..0000000 --- a/src/utils/valueToString.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) The ANTLR Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.txt file in the project root. - */ - -export function valueToString(v) { - return v === null ? "null" : v; -} diff --git a/tests/benchmarks/ParseServiceJS.ts b/tests/benchmarks/ParseServiceJS.ts index aa2c4a5..fa479ac 100644 --- a/tests/benchmarks/ParseServiceJS.ts +++ b/tests/benchmarks/ParseServiceJS.ts @@ -5,10 +5,12 @@ /* eslint-disable no-underscore-dangle */ -import { - CommonTokenStream, CharStreams, ParseTree, BailErrorStrategy, PredictionMode, ParseCancellationException, -} from "antlr4ng"; - +import { PredictionMode } from "../../src/atn/PredictionMode.js"; +import { BailErrorStrategy } from "../../src/BailErrorStrategy.js"; +import { CharStreams } from "../../src/CharStreams.js"; +import { CommonTokenStream } from "../../src/CommonTokenStream.js"; +import { ParseCancellationException } from "../../src/misc/ParseCancellationException.js"; +import { ParseTree } from "../../src/tree/ParseTree.js"; import { MySQLLexer } from "./generated/MySQLLexer.js"; import { MySQLParser } from "./generated/MySQLParser.js"; import { IParserErrorInfo, MySQLParseUnit } from "./support/helpers.js"; diff --git a/tests/benchmarks/run-benchmarks.ts b/tests/benchmarks/run-benchmarks.ts index d5c287d..a77a7f6 100644 --- a/tests/benchmarks/run-benchmarks.ts +++ b/tests/benchmarks/run-benchmarks.ts @@ -185,14 +185,14 @@ const parseFiles = () => { }); }; -const parserRun = () => { +const parserRun = (index: number) => { const timestamp = performance.now(); try { parseFiles(); } catch (e) { console.error(e); } finally { - console.log("Parse run took " + (performance.now() - timestamp) + " ms"); + console.log(`Parse run ${index} took ${(performance.now() - timestamp)} ms`); } }; @@ -205,9 +205,13 @@ splitterTest(); console.log("Splitter tests took " + (performance.now() - timestamp) + " ms"); console.log("Running antlr4ng parser (cold) ..."); -parserRun(); +parserRun(0); console.log("Running antlr4ng parser (warm) ..."); -parserRun(); +parserRun(1); +//parserRun(2); +//parserRun(3); +//parserRun(4); +//parserRun(5); console.log("Done"); diff --git a/tests/benchmarks/support/MySQLBaseLexer.ts b/tests/benchmarks/support/MySQLBaseLexer.ts index 58829b4..6cc1f14 100644 --- a/tests/benchmarks/support/MySQLBaseLexer.ts +++ b/tests/benchmarks/support/MySQLBaseLexer.ts @@ -24,8 +24,7 @@ /* eslint-disable no-underscore-dangle */ /* cspell: ignore ULONGLONG, MULT, MAXDB */ -import { Lexer, Token } from "antlr4ng"; - +import { Lexer, Token } from "../../../src/index.js"; import { MySQLLexer } from "../generated/MySQLLexer.js"; import { IMySQLRecognizerCommon, SqlMode } from "./MySQLRecognizerCommon.js"; diff --git a/tests/benchmarks/support/MySQLBaseRecognizer.ts b/tests/benchmarks/support/MySQLBaseRecognizer.ts index 4b8f379..786372f 100644 --- a/tests/benchmarks/support/MySQLBaseRecognizer.ts +++ b/tests/benchmarks/support/MySQLBaseRecognizer.ts @@ -21,8 +21,7 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -import { Parser } from "antlr4ng"; - +import { Parser } from "../../../src/Parser.js"; import { IMySQLRecognizerCommon, SqlMode } from "./MySQLRecognizerCommon.js"; export abstract class MySQLBaseRecognizer extends Parser implements IMySQLRecognizerCommon { diff --git a/tests/benchmarks/support/MySQLErrorListener.ts b/tests/benchmarks/support/MySQLErrorListener.ts index c3fce3e..b9241e0 100644 --- a/tests/benchmarks/support/MySQLErrorListener.ts +++ b/tests/benchmarks/support/MySQLErrorListener.ts @@ -32,9 +32,9 @@ import { MySQLLexer } from "../generated/MySQLLexer.js"; import { ErrorReportCallback } from "../support/helpers.js"; import { - BaseErrorListener, FailedPredicateException, IntervalSet, ATNSimulator, - NoViableAltException, RecognitionException, Recognizer, Token, -} from "antlr4ng"; + ATNSimulator, BaseErrorListener, FailedPredicateException, IntervalSet, NoViableAltException, + RecognitionException, Recognizer, Token, +} from "../../../src/index.js"; class Vocabulary { public getDisplayName(_symbol: number): string { @@ -48,7 +48,7 @@ class LexerNoViableAltException extends RecognitionException { class InputMismatchException extends RecognitionException { } -export class MySQLErrorListener extends BaseErrorListener { +export class MySQLErrorListener extends BaseErrorListener { private static simpleRules: Set = new Set([ MySQLParser.RULE_identifier, @@ -99,8 +99,8 @@ export class MySQLErrorListener extends BaseErrorListener { super(); } - public override syntaxError(recognizer: Recognizer, offendingSymbol: T | null, - line: number, charPositionInLine: number, msg: string, e: RecognitionException | null): void { + public override syntaxError = (recognizer: Recognizer, offendingSymbol: T | null, + line: number, charPositionInLine: number, msg: string, e: RecognitionException | null): void => { let message = ""; @@ -149,11 +149,11 @@ export class MySQLErrorListener extends BaseErrorListener { // Walk up from generic rules to reach something that gives us more context, if needed. let context = parser.context; - while (MySQLErrorListener.simpleRules.has(context.ruleIndex) && context.parent) { + while (context?.parent && MySQLErrorListener.simpleRules.has(context.ruleIndex)) { context = context.parent; } - switch (context.ruleIndex) { + switch (context?.ruleIndex) { case MySQLParser.RULE_functionCall: expectedText = "a complete function call or other expression"; break; @@ -329,7 +329,7 @@ export class MySQLErrorListener extends BaseErrorListener { } } - } + }; private intervalToString(set: IntervalSet, maxCount: number, vocabulary: Vocabulary): string { //const symbols = set.toList(); diff --git a/tests/benchmarks/support/MySQLRecognizerCommon.ts b/tests/benchmarks/support/MySQLRecognizerCommon.ts index ab86c98..cbf3b3e 100644 --- a/tests/benchmarks/support/MySQLRecognizerCommon.ts +++ b/tests/benchmarks/support/MySQLRecognizerCommon.ts @@ -21,8 +21,8 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -import { ParseTree, ParserRuleContext, RuleContext, TerminalNode, Token } from "antlr4ng"; - +import { ParseTree, ParserRuleContext, RuleContext, Token } from "../../../src/index.js"; +import { TerminalNode } from "../../../src/tree/TerminalNode.js"; import { MySQLParser, TextLiteralContext } from "../generated/MySQLParser.js"; // This interface describes functionality found in both, lexer and parser classes. @@ -37,7 +37,7 @@ export interface IMySQLRecognizerCommon { } // SQL modes that control parsing behavior. -export enum SqlMode { +export const enum SqlMode { NoMode, AnsiQuotes, HighNotPrecedence, @@ -61,11 +61,11 @@ export const getText = (context: RuleContext, convertEscapes: boolean): string = // TODO: take the optional repertoire prefix into account. let result = ""; - for (let index = 0; index < (context.children ?? []).length; ++index) { + for (let index = 0; index < context.getChildCount(); ++index) { const child = context.textStringLiteral(index)!; // eslint-disable-next-line no-underscore-dangle const token = child._value; - if (token.type === MySQLParser.DOUBLE_QUOTED_TEXT || token.type === MySQLParser.SINGLE_QUOTED_TEXT) { + if (token?.type === MySQLParser.DOUBLE_QUOTED_TEXT || token?.type === MySQLParser.SINGLE_QUOTED_TEXT) { let text = token.text || "''"; const quoteChar = text[0]; const doubledQuoteChar = quoteChar.repeat(2); @@ -156,9 +156,9 @@ export const sourceTextForRange = (start: Token | ParseTree, stop: Token | Parse stopToken = (stop instanceof TerminalNode) ? stop.symbol : (stop as ParserRuleContext).start; } - const stream = startToken?.getTokenSource()?.inputStream; + const stream = startToken?.tokenSource?.inputStream; const stopIndex = stop && stopToken ? stopToken.stop : 1e100; - let result = stream?.getText(startToken!.start, stopIndex) ?? ""; + let result = stream?.getText(startToken ? startToken.start : 0, stopIndex) ?? ""; if (keepQuotes || result.length < 2) { return result; } diff --git a/tests/benchmarks/support/helpers.ts b/tests/benchmarks/support/helpers.ts index 1ecce28..3a7d778 100644 --- a/tests/benchmarks/support/helpers.ts +++ b/tests/benchmarks/support/helpers.ts @@ -16,7 +16,7 @@ export interface TextSpan { } /** Indicates how a statement ends. */ -export enum StatementFinishState { +export const enum StatementFinishState { /** Ends with a delimiter. */ Complete, @@ -33,7 +33,7 @@ export enum StatementFinishState { DelimiterChange, } -export enum MySQLParseUnit { +export const enum MySQLParseUnit { Generic, CreateSchema, CreateTable, diff --git a/tests/rewriter/TokenStreamRewriter.spec.ts b/tests/rewriter/TokenStreamRewriter.spec.ts index 22e6397..9aeb877 100644 --- a/tests/rewriter/TokenStreamRewriter.spec.ts +++ b/tests/rewriter/TokenStreamRewriter.spec.ts @@ -13,13 +13,13 @@ import { Calc } from "./generatedCode/calc.js"; /** * - * @param {antlr4.Lexer} lexerClass The lexer class to use. - * @param {string} input The input to lex. + * @param lexerClass The lexer class to use. + * @param input The input to lex. * * @returns A new TokenStreamRewriter instance. */ const getRewriter = (lexerClass: typeof antlr4.Lexer, input: string) => { - const chars = new antlr4.InputStream(input); + const chars = new antlr4.CharStream(input); // @ts-ignore const lexer: antlr4.Lexer = new lexerClass(chars); @@ -403,7 +403,7 @@ describe("TokenStreamRewriter", () => { it("throws an error if second replace operation overlaps the first one on the left", () => { // Arrange - const chars = new antlr4.InputStream("abcccba"); + const chars = new antlr4.CharStream("abcccba"); const lexer = new ABC(chars); const tokens = new antlr4.CommonTokenStream(lexer); tokens.fill(); diff --git a/tests/rewriter/generatedCode/abc.ts b/tests/rewriter/generatedCode/abc.ts index 07b0633..35a7e90 100644 --- a/tests/rewriter/generatedCode/abc.ts +++ b/tests/rewriter/generatedCode/abc.ts @@ -12,7 +12,6 @@ const atn = new antlr4.ATNDeserializer().deserialize(serializedATN); const decisionsToDFA = atn.decisionToState.map((ds, index) => { return new antlr4.DFA(ds, index); }); export class ABC extends antlr4.Lexer { - public static readonly EOF = antlr4.Token.EOF; public static readonly A = 1; public static readonly B = 2; public static readonly C = 3; diff --git a/tests/rewriter/generatedCode/calc.ts b/tests/rewriter/generatedCode/calc.ts index 5cdedbc..2fbfa1e 100644 --- a/tests/rewriter/generatedCode/calc.ts +++ b/tests/rewriter/generatedCode/calc.ts @@ -20,7 +20,6 @@ const atn = new antlr4.ATNDeserializer().deserialize(serializedATN); const decisionsToDFA = atn.decisionToState.map((ds, index) => { return new antlr4.DFA(ds, index); }); export class Calc extends antlr4.Lexer { - public static readonly EOF = antlr4.Token.EOF; public static readonly ID = 1; public static readonly INT = 2; public static readonly SEMI = 3; diff --git a/tsconfig.json b/tsconfig.json index 9852f0a..0e154ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,23 @@ { "compilerOptions": { - "target": "ES6", + "target": "ESNext", "module": "Node16", "moduleResolution": "Node16", "sourceMap": true, "esModuleInterop": true, "noEmit": true, "lib": [ - "ES2020" + "ESNext", + "DOM" ], "noImplicitAny": true, "strictNullChecks": true, - "preserveConstEnums": true + "preserveConstEnums": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noFallthroughCasesInSwitch": false, // Switch to true when string hash in helpers.ts is no longer needed. + "forceConsistentCasingInFileNames": true, }, "include": [ "src/**/*.ts",