diff --git a/.vscode/launch.json b/.vscode/launch.json index 56a9821..ab064d4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "antlr-debug", + "request": "launch", + "name": "Debug Current Grammar", + "input": "tests/input.txt", + "grammar": "${file}", + "visualParseTree": true + }, { "type": "node", "request": "launch", diff --git a/src/ParserRuleContext.ts b/src/ParserRuleContext.ts index 89e78ae..e21b73b 100644 --- a/src/ParserRuleContext.ts +++ b/src/ParserRuleContext.ts @@ -221,11 +221,15 @@ export class ParserRuleContext implements ParseTree { } public getSourceInterval(): Interval { - if (this.start === null || this.stop === null) { + if (this.start === null) { return Interval.INVALID_INTERVAL; - } else { - return new Interval(this.start.tokenIndex, this.stop.tokenIndex); } + + if (this.stop === null || this.stop.tokenIndex < this.start.tokenIndex) { + return new Interval(this.start.tokenIndex, this.start.tokenIndex - 1); + } + + return new Interval(this.start.tokenIndex, this.stop.tokenIndex); } public depth(): number { diff --git a/src/atn/DecisionInfo.ts b/src/atn/DecisionInfo.ts index 451b7a8..0c7e8b8 100644 --- a/src/atn/DecisionInfo.ts +++ b/src/atn/DecisionInfo.ts @@ -205,7 +205,7 @@ export class DecisionInfo { this.predicateEvals = []; } - public toString1(): string { + public toString(): string { return "{" + "decision=" + this.decision + ", contextSensitivities=" + this.contextSensitivities.length + diff --git a/src/atn/Transition.ts b/src/atn/Transition.ts index 83df3ff..b8e344a 100644 --- a/src/atn/Transition.ts +++ b/src/atn/Transition.ts @@ -57,6 +57,10 @@ export abstract class Transition { return null; } + public toString(): string { + return ""; + } + public abstract get transitionType(): number; public abstract matches(symbol: number, minVocabSymbol: number, maxVocabSymbol: number): boolean; } diff --git a/src/misc/Interval.ts b/src/misc/Interval.ts index f028c74..af3c89d 100644 --- a/src/misc/Interval.ts +++ b/src/misc/Interval.ts @@ -20,14 +20,8 @@ export class Interval { private cachedHashCode: number; public constructor(start: number, stop: number) { - if (start <= stop) { - this.start = start; - this.stop = stop; - } else { - this.start = stop; - this.stop = start; - } - + this.start = start; + this.stop = stop; this.cachedHashCode = Math.imul(651 + start, 31) + stop; } @@ -136,11 +130,7 @@ export class Interval { } public toString(): string { - if (this.start === this.stop) { - return this.start.toString(); - } else { - return this.start.toString() + ".." + this.stop.toString(); - } + return `${this.start}..${this.stop}`; } public get length(): number { diff --git a/src/misc/IntervalSet.ts b/src/misc/IntervalSet.ts index 8684422..384045b 100644 --- a/src/misc/IntervalSet.ts +++ b/src/misc/IntervalSet.ts @@ -28,9 +28,15 @@ export class IntervalSet { private cachedHashCode: number | undefined; - public constructor(set?: IntervalSet) { + public constructor(set?: IntervalSet | number[]) { if (set) { - this.addSet(set); + if (Array.isArray(set)) { + for (const el of set) { + this.addOne(el); + } + } else { + this.addSet(set); + } } } diff --git a/src/tree/pattern/ParseTreePatternMatcher.ts b/src/tree/pattern/ParseTreePatternMatcher.ts index 653bfa0..716a06c 100644 --- a/src/tree/pattern/ParseTreePatternMatcher.ts +++ b/src/tree/pattern/ParseTreePatternMatcher.ts @@ -62,7 +62,7 @@ import { TokenTagToken } from "./TokenTagToken.js"; * * See `TestParseTreeMatcher` for lots of examples. * {@link ParseTreePattern} has two static helper methods: - * {@link ParseTreePattern#findAll} and {@link ParseTreePattern#match} that + * {@link ParseTreePattern.findAll} and {@link ParseTreePattern#match} that * are easy to use but not super efficient because they create new * {@link ParseTreePatternMatcher} objects each time and have to compile the * pattern in string form before using it. @@ -84,7 +84,7 @@ import { TokenTagToken } from "./TokenTagToken.js"; * * Delimiters are `<` and `>`, with `\` as the escape string * by default, but you can set them to whatever you want using - * {@link #setDelimiters}. You must escape both start and stop strings + * {@link ParseTreePatternMatcher.setDelimiters}. You must escape both start and stop strings * `\<` and `\>`. */ export class ParseTreePatternMatcher { diff --git a/tests/BitSet.spec.ts b/tests/api/BitSet.spec.ts similarity index 98% rename from tests/BitSet.spec.ts rename to tests/api/BitSet.spec.ts index f630ec6..9cd4ddb 100644 --- a/tests/BitSet.spec.ts +++ b/tests/api/BitSet.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { BitSet } from "../src/misc/BitSet.js"; +import { BitSet } from "../../src/misc/BitSet.js"; describe("BitSet", () => { it("Initialize with all bits set to false", () => { diff --git a/tests/CharStream.spec.ts b/tests/api/CharStream.spec.ts similarity index 99% rename from tests/CharStream.spec.ts rename to tests/api/CharStream.spec.ts index 1f597a9..fe0f0a2 100644 --- a/tests/CharStream.spec.ts +++ b/tests/api/CharStream.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { CharStreamImpl as CharStream, IntStream, Interval } from "../src/index.js"; +import { CharStreamImpl as CharStream, IntStream, Interval } from "../../src/index.js"; const unicodeInput = "Hello 👋, World! 😁"; diff --git a/tests/HashSet.spec.ts b/tests/api/HashSet.spec.ts similarity index 97% rename from tests/HashSet.spec.ts rename to tests/api/HashSet.spec.ts index 499269a..ae2398f 100644 --- a/tests/HashSet.spec.ts +++ b/tests/api/HashSet.spec.ts @@ -6,8 +6,8 @@ import { describe, it, expect, beforeEach } from "vitest"; -import { HashSet } from "../src/misc/HashSet.js"; -import type { IComparable } from "../src/utils/helpers.js"; +import { HashSet } from "../../src/misc/HashSet.js"; +import type { IComparable } from "../../src/utils/helpers.js"; class Number implements IComparable { public constructor(public value: number) { } diff --git a/tests/IntervalSet.spec.ts b/tests/api/IntervalSet.spec.ts similarity index 99% rename from tests/IntervalSet.spec.ts rename to tests/api/IntervalSet.spec.ts index 8b74112..53a5484 100644 --- a/tests/IntervalSet.spec.ts +++ b/tests/api/IntervalSet.spec.ts @@ -6,7 +6,7 @@ import { describe, it, expect } from "vitest"; -import * as antlr4 from "../src/index.js"; +import * as antlr4 from "../../src/index.js"; // eslint-disable-next-line @typescript-eslint/naming-convention const IntervalSet = antlr4.IntervalSet; diff --git a/tests/api/XPath.spec.ts b/tests/api/XPath.spec.ts new file mode 100644 index 0000000..1856693 --- /dev/null +++ b/tests/api/XPath.spec.ts @@ -0,0 +1,94 @@ +/* + * 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 { beforeAll, describe, expect, it } from "vitest"; + +import { CharStream, CommonTokenStream, ParserRuleContext, XPath, type ParseTree, type TerminalNode } from "antlr4ng"; + +import { ExprLexer } from "../generated/ExprLexer.js"; +import { ExprParser } from "../generated/ExprParser.js"; + +describe("XPath", () => { + const input = "def f(x,y) { x = 3+4; y; ; }\ndef g(x) { return 1+2*x; }\n"; + let parser!: ExprParser; + let parseTree: ParseTree; + + const xpath = [ + "/prog/func", // all funcs under prog at root + "/prog/*", // all children of prog at root + "/*/func", // all func kids of any root node + "prog", // prog must be root node + "/prog", // prog must be root node + "/*", // any root + "*", // any root + "//ID", // any ID in tree + "//expr/primary/ID",// any ID child of a primary under any expr + "//body//ID", // any ID under a body + "//'return'", // any 'return' literal in tree, matched by literal name + "//RETURN", // any 'return' literal in tree, matched by symbolic name + "//primary/*", // all kids of any primary + "//func/*/stat", // all stat nodes grand kids of any func node + "/prog/func/'def'", // all def literal kids of func kid of prog + "//stat/';'", // all ';' under any stat node + "//expr/primary/!ID", // anything but ID under primary under any expr node + "//expr/!primary", // anything but primary under any expr node + "//!*", // nothing anywhere + "/!*", // nothing at root + "//expr//ID", // any ID under any expression (tests antlr/antlr4#370) + ]; + const expected = [ + "[func, func]", + "[func, func]", + "[func, func]", + "[prog]", + "[prog]", + "[prog]", + "[prog]", + "[f, x, y, x, y, g, x, x]", + "[y, x]", + "[x, y, x]", + "[return]", + "[return]", + "[3, 4, y, 1, 2, x]", + "[stat, stat, stat, stat]", + "[def, def]", + "[;, ;, ;, ;]", + "[3, 4, 1, 2]", + "[expr, expr, expr, expr, expr, expr]", + "[]", + "[]", + "[y, x]", + ]; + + beforeAll(() => { + const lexer = new ExprLexer(CharStream.fromString(input)); + const tokens = new CommonTokenStream(lexer); + parser = new ExprParser(tokens); + parseTree = parser.prog(); + }); + + it("Successful matches", () => { + for (let i = 0; i < xpath.length; i++) { + const found = XPath.findAll(parseTree, xpath[i], parser); + + const ruleNames: string[] = []; + for (const t of found) { + if (t instanceof ParserRuleContext) { + const r = t; + ruleNames.push(parser.ruleNames[r.ruleIndex]); + } else { + const token = t as TerminalNode; + ruleNames.push(token.getText()); + } + } + + const result = `[${ruleNames.join(", ")}]`; + + expect(result, "path " + xpath[i] + " failed").to.equal(expected[i]); + } + + }); +}); diff --git a/tests/fixtures/grammars/Expr.g4 b/tests/fixtures/grammars/Expr.g4 new file mode 100644 index 0000000..67e8441 --- /dev/null +++ b/tests/fixtures/grammars/Expr.g4 @@ -0,0 +1,75 @@ +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +grammar Expr; + +prog + : func+ + ; + +func + : 'def' ID '(' arg (',' arg)* ')' body + ; + +body + : '{' stat+ '}' + ; + +arg + : ID + ; + +stat + : expr ';' # printExpr + | ID '=' expr ';' # assign + | 'return' expr ';' # ret + | ';' # blank + ; + +expr + : expr ('*' | '/') expr # MulDiv + | expr ('+' | '-') expr # AddSub + | primary # prim + ; + +primary + : INT # int + | ID # id + | '(' expr ')' # parens + ; + +MUL + : '*' + ; + +DIV + : '/' + ; + +ADD + : '+' + ; + +SUB + : '-' + ; + +RETURN + : 'return' + ; + +ID + : [a-zA-Z]+ + ; + +INT + : [0-9]+ + ; + +NEWLINE + : '\r'? '\n' -> skip + ; + +WS + : [ \t]+ -> skip + ;