antlr4-runtime
Version:
JavaScript runtime for ANTLR4
716 lines (549 loc) • 21.5 kB
JavaScript
import antlr4 from "../../src/antlr4/index.node.js";
import abc from "./generatedCode/abc.js";
import calc from "./generatedCode/calc.js";
/**
*
* @param {antlr4.Lexer} lexerClass
* @param {string} input
*/
function getRewriter(lexerClass, input) {
const chars = new antlr4.InputStream(input);
const lexer = new lexerClass(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
tokens.fill();
return new antlr4.TokenStreamRewriter(tokens);
}
describe("TokenStreamRewriter", () => {
it("inserts '0' before index 0", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "0");
// Assert
expect(rewriter.getText()).toEqual("0abc");
});
it("inserts 'x' after last index", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertAfter(2, "x");
// Assert
expect(rewriter.getText()).toEqual("abcx");
});
it("inserts 'x' after the 'b' token", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
const bToken = rewriter.tokens.get(1);
// Act
rewriter.insertAfter(bToken, "x");
// Assert
expect(rewriter.getText()).toEqual("abxc");
});
it("inserts 'x' at the end if the index is out of bounds", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertAfter(100, "x");
// Assert
expect(rewriter.getText()).toEqual("abcx");
});
it("inserts 'x' before the 'b' token", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
const bToken = rewriter.tokens.get(1);
// Act
rewriter.insertBefore(bToken, "x");
// Assert
expect(rewriter.getText()).toEqual("axbc");
});
it("inserts 'x' before and after middle index", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(1, "x");
rewriter.insertAfter(1, "x");
// Assert
expect(rewriter.getText()).toEqual("axbxc");
});
it("replaces the first token with an 'x'", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(0, "x");
// Assert
expect(rewriter.getText()).toEqual("xbc");
});
it("replaces the last token with an 'x'", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(2, "x");
// Assert
expect(rewriter.getText()).toEqual("abx");
});
it("replaces the middle token with an 'x'", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(1, "x");
// Assert
expect(rewriter.getText()).toEqual("axc");
});
it("calls getText() with different start/stop arguments (1 of 2)", () => {
// Arrange
const rewriter = getRewriter(calc, "x = 3 * 0;");
// Act
rewriter.replace(4, 8, "0"); // replace 3 * 0 with 0
// Assert
expect(rewriter.getTokenStream().getText()).toEqual("x = 3 * 0;");
expect(rewriter.getText()).toEqual("x = 0;");
expect(rewriter.getText(new antlr4.Interval(0, 9))).toEqual("x = 0;");
expect(rewriter.getText(new antlr4.Interval(4, 8))).toEqual("0");
});
it("calls getText() with different start/stop arguments (2 of 2)", () => {
// Arrange
const rewriter = getRewriter(calc, "x = 3 * 0 + 2 * 0;");
// Act/Assert
expect(rewriter.getTokenStream().getText()).toEqual("x = 3 * 0 + 2 * 0;");
rewriter.replace(4, 8, "0"); // replace 3 * 0 with 0
expect(rewriter.getText()).toEqual("x = 0 + 2 * 0;");
expect(rewriter.getText(new antlr4.Interval(0, 17))).toEqual("x = 0 + 2 * 0;");
expect(rewriter.getText(new antlr4.Interval(4, 8))).toEqual("0");
expect(rewriter.getText(new antlr4.Interval(0, 8))).toEqual("x = 0");
expect(rewriter.getText(new antlr4.Interval(12, 16))).toEqual("2 * 0");
rewriter.insertAfter(17, "// comment");
expect(rewriter.getText(new antlr4.Interval(12, 18))).toEqual("2 * 0;// comment");
expect(rewriter.getText(new antlr4.Interval(0, 8))).toEqual("x = 0");
});
it("replaces the middle index, twice", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(1, "x");
rewriter.replaceSingle(1, "y");
// Assert
expect(rewriter.getText()).toEqual("ayc");
});
it("inserts '_' at the beginning and then replaces the middle token, twice", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "_");
rewriter.replaceSingle(1, "x");
rewriter.replaceSingle(1, "y");
// Assert
expect(rewriter.getText()).toEqual("_ayc");
});
it("replaces, then deletes the middle index", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(1, "x");
rewriter.delete(1);
// Assert
expect(rewriter.getText()).toEqual("ac");
});
it("throws an error when inserting into a replaced segment", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replace(0, 2, "x");
rewriter.insertBefore(1, "0");
// Assert
expect(() => rewriter.getText()).toThrowError(
"insert op <InsertBeforeOp@[@1,1:1='b',<2>,1:1]:\"0\"> within boundaries of previous <ReplaceOp@[@0,0:0='a',<1>,1:0]..[@2,2:2='c',<3>,1:2]:\"x\">"
);
});
it("throws an error when inserting into a deleted segment", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.delete(0, 2);
rewriter.insertBefore(1, "0");
// Assert
expect(() => rewriter.getText()).toThrowError(
"insert op <InsertBeforeOp@[@1,1:1='b',<2>,1:1]:\"0\"> within boundaries of previous <DeleteOp@[@0,0:0='a',<1>,1:0]..[@2,2:2='c',<3>,1:2]>"
);
});
it("inserts '0' before the first token and then replaces it with an 'x'", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "0");
rewriter.replaceSingle(0, "x");
// Assert
expect(rewriter.getText()).toEqual("0xbc");
});
it("inserts texts in reverse order when multiple inserts occur at the same index", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(1, "x");
rewriter.insertBefore(1, "y");
// Assert
expect(rewriter.getText()).toEqual("ayxbc");
});
it("inserts 'y' and 'x' before the first index and then replaces it with 'z'", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "x");
rewriter.insertBefore(0, "y");
rewriter.replaceSingle(0, "z");
// Assert
expect(rewriter.getText()).toEqual("yxzbc");
});
it("replaces the last index with an 'x' and then inserts 'y' before it", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(2, "x");
rewriter.insertBefore(2, "y");
// Assert
expect(rewriter.getText()).toEqual("abyx");
});
it("replaces thte last index with an 'x' and then inserts 'y' after it", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replaceSingle(2, "x");
rewriter.insertAfter(2, "y");
// Assert
expect(rewriter.getText()).toEqual("abxy");
});
it("replaces a range with an 'x' and then inserts 'y' before the left edge of the range", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(2, 4, "x");
rewriter.insertBefore(2, "y");
// Assert
expect(rewriter.getText()).toEqual("abyxba");
});
it("throws an error if an attempt is made to insert a token before the right edge of a replaced range", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(2, 4, "x");
rewriter.insertBefore(4, "y");
// Assert
expect(() => rewriter.getText()).toThrowError(
"insert op <InsertBeforeOp@[@4,4:4='c',<3>,1:4]:\"y\"> within boundaries of previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"x\">"
);
});
it("replaces a range with an 'x' then inserts 'y' after the right edge of the range", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(2, 4, "x");
rewriter.insertAfter(4, "y");
// Assert
expect(rewriter.getText()).toEqual("abxyba");
});
it("replaces a token range", () => {
// Arrange
const rewriter = getRewriter(abc, "abcba");
const bToken = rewriter.tokens.get(1);
const dToken = rewriter.tokens.get(3);
// Act
rewriter.replace(bToken, dToken, "x");
// Assert
expect(rewriter.getText()).toEqual("axa");
});
it("throws an error when replace is given an invalid range", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
const badRanges = [
[1, 0], // from > to
[-1, 1], // from is negative
[1, -1], // to is negative
[-2, -1], // both are negative
[1, 4] // to is out of bounds
];
// Act/Assert
for (const [from, to] of badRanges) {
expect(() => rewriter.replace(from, to, "x")).toThrow();
}
});
it("replaces all tokens with an 'x'", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(0, 6, "x");
// Assert
expect(rewriter.getText()).toEqual("x");
});
it("replaces the middle 'ccc' with 'xyz'", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(2, 4, "xyz");
// Assert
expect(rewriter.getText(new antlr4.Interval(0, 6))).toEqual("abxyzba");
});
it("throws an error if second replace operation overlaps the first one on the right", () => {
// Arrange
const rewriter = getRewriter(abc, "abcccba");
// Act
rewriter.replace(2, 4, "xyz");
rewriter.replace(3, 5, "foo");
// Assert
expect(() => rewriter.getText()).toThrowError(
"replace op boundaries of <ReplaceOp@[@3,3:3='c',<3>,1:3]..[@5,5:5='b',<2>,1:5]:\"foo\"> overlap with previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"xyz\">"
);
});
it("throws an error if second replace operation overlaps the first one on the left", () => {
// Arrange
const chars = new antlr4.InputStream("abcccba");
const lexer = new abc(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
tokens.fill();
const rewriter = new antlr4.TokenStreamRewriter(tokens);
// Act
rewriter.replace(2, 4, "xyz");
rewriter.replace(1, 3, "foo");
// Assert
expect(() => rewriter.getText()).toThrowError(
"replace op boundaries of <ReplaceOp@[@1,1:1='b',<2>,1:1]..[@3,3:3='c',<3>,1:3]:\"foo\"> overlap with previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"xyz\">"
);
});
it("ignores first replace operation when the second one overlaps it on both sides (superset)", () => {
// Arrange
const rewriter = getRewriter(abc, "abcba");
// Act
rewriter.replace(2, 2, "xyz");
rewriter.replace(0, 3, "foo");
// Assert
expect(rewriter.getText()).toEqual("fooa");
});
it("inserts 'x' and 'y' before the first token", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "x");
rewriter.insertBefore(0, "y");
// Assert
expect(rewriter.getText()).toEqual("yxabc");
});
it("performs 3 inserts at 2 locations", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(1, "x");
rewriter.insertBefore(0, "y");
rewriter.insertBefore(1, "z");
// Assert
expect(rewriter.getText()).toEqual("yazxbc");
});
it("replaces 'abc' with 'foo' and then inserts 'z' before it", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.replace(0, 2, "foo");
rewriter.insertBefore(0, "z");
// Assert
expect(rewriter.getText()).toEqual("zfoo");
});
it("deletes 'abc' and then inserts 'z' before it", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.delete(0, 2);
rewriter.insertBefore(0, "z");
// Assert
expect(rewriter.getText()).toEqual("z");
});
it("makes 3 inserts at 3 locations", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(1, "x");
rewriter.insertBefore(2, "y");
rewriter.insertBefore(0, "z");
// Assert
expect(rewriter.getText()).toEqual("zaxbyc");
});
it("throws an error if second replace operation affects a subset of a previous one", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.replace(0, 3, "bar");
rewriter.replace(1, 2, "foo");
// Assert
expect(() => rewriter.getText()).toThrowError(
"replace op boundaries of <ReplaceOp@[@1,1:1='b',<2>,1:1]..[@2,2:2='c',<3>,1:2]:\"foo\"> overlap with previous <ReplaceOp@[@0,0:0='a',<1>,1:0]..[@3,3:3='c',<3>,1:3]:\"bar\">"
);
});
it("ignores the first replace operation when the secone one extends it to the left", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.replace(1, 2, "foo");
rewriter.replace(0, 2, "bar");
// Assert
expect(rewriter.getText()).toEqual("barc");
});
it("ignores the first replace operation when the secone one extends it to the right", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.replace(1, 2, "foo");
rewriter.replace(1, 3, "bar");
// Assert
expect(rewriter.getText()).toEqual("abar");
});
it("only applies one replace operation when identical ones are given", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.replace(1, 2, "foo");
rewriter.replace(1, 2, "foo");
// Assert
expect(rewriter.getText()).toEqual("afooc");
});
it("drops the insert operation when it is covered by a subsequent replace operation", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(2, "foo");
rewriter.replace(1, 2, "foo");
// Assert
expect(rewriter.getText()).toEqual("afoo");
});
it("performs the insert operation when disjoint from the replace operation (1 of 2)", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.insertBefore(1, "x");
rewriter.replace(2, 3, "foo");
// Assert
expect(rewriter.getText()).toEqual("axbfoo");
});
it("performs the insert operation when disjoint from the replace operation (2 of 2)", () => {
// Arrange
const rewriter = getRewriter(abc, "abcc");
// Act
rewriter.replace(2, 3, "foo");
rewriter.insertBefore(1, "x");
// Assert
expect(rewriter.getText()).toEqual("axbfoo");
});
it("inserts 'y' before the last token, then deletes it", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(2, "y");
rewriter.delete(2);
// Assert
expect(rewriter.getText()).toEqual("aby");
});
it("deletes the 'a' token", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
const aToken = rewriter.tokens.get(0);
// Act
rewriter.delete(aToken);
// Assert
expect(rewriter.getText()).toEqual("bc");
});
// Test for https://github.com/antlr/antlr4/issues/550
it("distinguishes between insertAfter and insertBefore to preserve order (1 of 2)", () => {
// Arrange
const rewriter = getRewriter(abc, "aa");
// Act
rewriter.insertBefore(0, "<b>");
rewriter.insertAfter(0, "</b>");
rewriter.insertBefore(1, "<b>");
rewriter.insertAfter(1, "</b>");
// Assert
expect(rewriter.getText()).toEqual("<b>a</b><b>a</b>");
});
it("distinguishes between insertAfter and insertBefore to preserve order (2 of 2)", () => {
// Arrange
const rewriter = getRewriter(abc, "aa");
// Act
rewriter.insertBefore(0, "<p>");
rewriter.insertBefore(0, "<b>");
rewriter.insertAfter(0, "</p>");
rewriter.insertAfter(0, "</b>");
rewriter.insertBefore(1, "<b>");
rewriter.insertAfter(1, "</b>");
// Assert
expect(rewriter.getText()).toEqual("<b><p>a</p></b><b>a</b>");
});
it("preserves the order of contiguous inserts", () => {
// Arrange
const rewriter = getRewriter(abc, "ab");
// Act
rewriter.insertBefore(0, "<p>");
rewriter.insertBefore(0, "<b>");
rewriter.insertBefore(0, "<div>");
rewriter.insertAfter(0, "</p>");
rewriter.insertAfter(0, "</b>");
rewriter.insertAfter(0, "</div>");
rewriter.insertBefore(1, "!");
// Assert
expect(rewriter.getText()).toEqual("<div><b><p>a</p></b></div>!b");
});
it("accepts different types as text", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, false);
rewriter.insertBefore(0, 0);
rewriter.insertBefore(0, {});
rewriter.insertBefore(0, []);
rewriter.insertBefore(0, "");
rewriter.insertBefore(0, null);
// Assert
expect(rewriter.getText()).toEqual("[object Object]0falseabc");
});
it("returns the original input if no rewrites have occurred", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
const result = rewriter.getText();
// Assert
expect(result).toEqual("abc");
});
it("segments operations by program name", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
rewriter.insertBefore(0, "b", "P1");
rewriter.insertAfter(0, "c", "P2");
rewriter.replaceSingle(2, "b", "P2");
// Assert
expect(rewriter.getText("P1")).toEqual("babc");
expect(rewriter.getText("P2")).toEqual("acbb");
});
it("doesn't make a fuss if getText is supplied with an interval that exceeds the token range", () => {
// Arrange
const rewriter = getRewriter(abc, "abc");
// Act
const unmodified = rewriter.getText(new antlr4.Interval(-1, 3));
rewriter.insertAfter(2, "a");
const modified = rewriter.getText(new antlr4.Interval(0, 200));
// Assert
expect(unmodified).toEqual("abc");
expect(modified).toEqual("abca");
});
it("ignores inserts that occur within a removed range", () => {
// Arrange
const rewriter = getRewriter(abc, "abcba");
// Act
rewriter.insertAfter(2, "c");
rewriter.delete(2, 3);
// Assert
expect(rewriter.getText()).toEqual("aba");
});
it("handles overlapping delete ranges", () => {
// Arrange
const rewriter = getRewriter(abc, "abcba");
// Act
rewriter.delete(1, 3);
rewriter.delete(2, 4);
// Assert
expect(rewriter.getText()).toEqual("a");
});
});