Skip to content

Commit

Permalink
Some bug fixes.
Browse files Browse the repository at this point in the history
- Added debug config for ANLTR4 grammars (for testing).
- Fixed a wrong method name in DecisionInfo.
- Removed automatic reordering of start and stop in Interval. It's by intention to accept whatever comes in. Also change Interval.toString() to conform with the Java runtime.
- IntervalSet accepts a list of values in its constructor, to ease creation for non-continuous value ranges.
- Fixed source interval retrieval in ParseRuleContext (now working like in Java).
- Moved isolated test specs to the API folder, to stay close to the other specs.
- Added a new test spec for XPath.

Signed-off-by: Mike Lischke <[email protected]>
  • Loading branch information
mike-lischke committed Dec 15, 2024
1 parent cdc69c2 commit 0df4c5f
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 26 deletions.
8 changes: 8 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 7 additions & 3 deletions src/ParserRuleContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/atn/DecisionInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class DecisionInfo {
this.predicateEvals = [];
}

public toString1(): string {
public toString(): string {
return "{" +
"decision=" + this.decision +
", contextSensitivities=" + this.contextSensitivities.length +
Expand Down
4 changes: 4 additions & 0 deletions src/atn/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
16 changes: 3 additions & 13 deletions src/misc/Interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 8 additions & 2 deletions src/misc/IntervalSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/tree/pattern/ParseTreePatternMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion tests/BitSet.spec.ts → tests/api/BitSet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/CharStream.spec.ts → tests/api/CharStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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! 😁";

Expand Down
4 changes: 2 additions & 2 deletions tests/HashSet.spec.ts → tests/api/HashSet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
94 changes: 94 additions & 0 deletions tests/api/XPath.spec.ts
Original file line number Diff line number Diff line change
@@ -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]);
}

});
});
75 changes: 75 additions & 0 deletions tests/fixtures/grammars/Expr.g4
Original file line number Diff line number Diff line change
@@ -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
;

0 comments on commit 0df4c5f

Please sign in to comment.