clarity-pattern-parser
Version:
Parsing Library for Typescript and Javascript.
315 lines (254 loc) • 11.2 kB
text/typescript
import { Cursor } from "./Cursor";
import { Node } from "../ast/Node";
import { Literal } from "./Literal";
import { Options } from "./Options";
import { Sequence } from "./Sequence";
import { Pattern } from "./Pattern";
import { Optional } from "./Optional";
import { Regex } from "./Regex";
import { Reference } from "./Reference";
import { Expression } from "./Expression";
describe("Options", () => {
test("Empty Options", () => {
expect(() => {
new Options("bad", []);
}).toThrow();
});
test("One Option Successful", () => {
const a = new Options("a", [new Literal("a", "A")]);
const cursor = new Cursor("A");
const result = a.parse(cursor);
const expected = new Node("literal", "a", 0, 0, [], "A")
expect(result?.isEqual(expected)).toBeTruthy();
});
test("One Option Failed", () => {
const a = new Options("a", [new Literal("a", "A")]);
const cursor = new Cursor("B");
const result = a.parse(cursor);
expect(result).toEqual(null);
expect(cursor.index).toBe(0);
expect(cursor.hasError).toBeTruthy();
});
test("Two Option", () => {
const a = new Options("a-b", [new Literal("a", "A"), new Literal("b", "B")]);
const cursor = new Cursor("AB");
let result = a.parse(cursor);
let expected = new Node("literal", "a", 0, 0, [], "A");
expect(result?.isEqual(expected)).toBeTruthy();
cursor.next();
result = a.parse(cursor);
expected = new Node("options", "a-b", 0, 0, [
new Node("literal", "b", 0, 0, [], "B")
], "B");
});
test("Get Tokens", () => {
const aOrB = new Options("a-b", [new Literal("a", "A"), new Literal("b", "B")]);
const tokens = aOrB.getTokens();
const expected = ["A", "B"];
expect(tokens).toEqual(expected);
});
test("Get Tokens After", () => {
const a = new Options("a", [new Literal("a", "A")]);
const parent = new Sequence("parent", [a, new Literal("b", "B")]);
const tokens = parent.children[0].getTokensAfter(parent.children[0].children[0]);
const expected = ["B"];
expect(tokens).toEqual(expected);
});
test("Get Tokens After Without A Parent", () => {
const a = new Options("a", [new Literal("a", "A")]);
const tokens = a.getTokensAfter(a.children[0]);
const expected: string[] = [];
expect(tokens).toEqual(expected);
});
test("Properties", () => {
const a = new Options("a", [new Literal("a", "A")]);
expect(a.type).toBe("options");
expect(a.name).toBe("a");
expect(a.parent).toBeNull();
expect(a.children[0].name).toBe("a");
});
test("Exec", () => {
const a = new Options("a", [new Literal("a", "A")]);
const { ast: result } = a.exec("B");
expect(result).toBeNull();
});
test("Test No Match", () => {
const a = new Options("a", [new Literal("a", "A")]);
const result = a.test("B");
expect(result).toBeFalsy();
});
test("Test With Match", () => {
const a = new Options("a", [new Literal("a", "A")]);
const result = a.test("A");
expect(result).toBeTruthy();
});
test("Get Next Tokens", () => {
const sequence = new Sequence("sequence", [
new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
]),
new Literal("c", "C")
]);
const orClone = sequence.find(p => p.name === "a-or-b") as Pattern;
const tokens = orClone.getNextTokens();
expect(tokens.length).toBe(1);
expect(tokens[0]).toBe("C");
});
test("Get Next Tokens With Null Parent", () => {
const or = new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
])
const tokens = or.getNextTokens();
expect(tokens.length).toBe(0);
});
test("Get Tokens After", () => {
const sequence = new Sequence("sequence", [
new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
]),
new Literal("c", "C")
]);
const aClone = sequence.find(p => p.name === "a") as Pattern;
const orClone = sequence.find(p => p.name === "a-or-b") as Pattern;
const tokens = orClone.getTokensAfter(aClone);
expect(tokens.length).toBe(1);
expect(tokens[0]).toBe("C");
});
test("Get Patterns", () => {
const aOrB = new Options("a-b", [new Literal("a", "A"), new Literal("b", "B")]);
const patterns = aOrB.getPatterns();
const expected = [
aOrB.find(p => p.name === "a"),
aOrB.find(p => p.name === "b")
];
expect(patterns).toEqual(expected);
});
test("Get Patterns After", () => {
const sequence = new Sequence("sequence", [
new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
]),
new Literal("c", "C")
]);
const aClone = sequence.find(p => p.name === "a") as Pattern;
const orClone = sequence.find(p => p.name === "a-or-b") as Pattern;
const patterns = orClone.getPatternsAfter(aClone);
expect(patterns.length).toBe(1);
expect(patterns[0].name).toBe("c");
});
test("Get Patterns After With Null Parent", () => {
const or = new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
])
const aClone = or.find(p => p.name === "a") as Pattern;
const patterns = or.getPatternsAfter(aClone);
expect(patterns.length).toBe(0);
});
test("Get Next Patterns", () => {
const sequence = new Sequence("sequence", [
new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
]),
new Literal("c", "C")
]);
const orClone = sequence.find(p => p.name === "a-or-b") as Pattern;
const patterns = orClone.getNextPatterns();
expect(patterns.length).toBe(1);
expect(patterns[0].name).toBe("c");
});
test("Get Next Patterns With Null Parent", () => {
const or = new Options("a-or-b", [
new Literal("a", "A"),
new Literal("b", "B")
])
const patterns = or.getNextPatterns();
expect(patterns.length).toBe(0);
});
test("Greedy With Match Last", () => {
const john = new Literal("john", "John");
const doe = new Literal("doe", "Doe");
const jane = new Literal("jane", "Jane");
const smith = new Literal("smith", "Smith");
const space = new Literal("space", " ");
const firstName = new Options("first-name", [john, jane], true);
const lastName = new Options("last-name", [doe, smith], true);
const johnJohnson = new Literal("john-johnson", "John Johnson");
const fullName = new Sequence("full-name", [firstName, space, lastName]);
const names = new Options("names", [fullName, johnJohnson], true);
const result = names.exec("John Johnson");
expect(result.ast?.value).toBe("John Johnson");
});
test("Greedy With Match First", () => {
const john = new Literal("john", "John");
const doe = new Literal("doe", "Doe");
const jane = new Literal("jane", "Jane");
const smith = new Literal("smith", "Smith");
const space = new Literal("space", " ");
const firstName = new Options("first-name", [john, jane], true);
const lastName = new Options("last-name", [doe, smith], true);
const johnJohnson = new Literal("john-johnson", "John Johnson");
const fullName = new Sequence("full-name", [firstName, space, lastName]);
const names = new Options("names", [johnJohnson, fullName], true);
const result = names.exec("John Johnson");
expect(result.ast?.value).toBe("John Johnson");
});
test("Greedy With Match In Middle", () => {
const john = new Literal("john", "John");
const doe = new Literal("doe", "Doe");
const jane = new Literal("jane", "Jane");
const smith = new Literal("smith", "Smith");
const space = new Literal("space", " ");
const firstName = new Options("first-name", [john, jane], true);
const lastName = new Options("last-name", [doe, smith], true);
const johnJohnson = new Literal("john-johnson", "John Johnson");
const johnStockton = new Literal("john-stockton", "John Stockton");
const fullName = new Sequence("full-name", [firstName, space, lastName]);
const names = new Options("names", [johnStockton, johnJohnson, fullName], true);
const result = names.exec("John Johnson");
expect(result.ast?.value).toBe("John Johnson");
});
// This doesn't make sense, but every pattern needs to handle a null result with no error.
test("Optional option", () => {
const john = new Optional("optional-john", new Literal("john", "John"));
const jane = new Literal("jane", "Jane");
const firstName = new Options("first-name", [john, jane], true);
const result = firstName.exec("Jane");
expect(result.ast?.value).toBe("Jane");
});
test("Cyclical Error Recorvery", () => {
const john = new Literal("john", "John");
const jane = new Literal("jane", "Jane");
const names = new Options("names", [john, jane]);
const questionMark = new Literal("?", "?");
const colon = new Literal(":", ":");
const space = new Regex("space", "\\s+");
const expressionReference = new Reference("expression");
const ternary = new Sequence("ternary", [expressionReference, space, questionMark, space, expressionReference, space, colon, space, expressionReference]);
const expression = new Expression("expression", [names, ternary]);
let result = expression.exec("John");
expect(result.ast?.toString()).toBe("John");
result = expression.exec("John ? Jane : John");
expect(result.ast?.toString()).toBe("John ? Jane : John");
result = expression.exec("John ? John ? Jane : John ? Jane : John : John");
expect(result.ast?.toString()).toBe("John ? John ? Jane : John ? Jane : John : John");
});
test("Deeper Cyclical Error Recorvery", () => {
const john = new Literal("john", "John");
const expressionReference = new Reference("expression");
const johns = new Sequence("johns", [john, expressionReference]);
const expression = new Options("expression", [
john,
johns,
], true);
let result = expression.exec("John");
expect(result.ast?.toString()).toBe("John");
result = expression.exec("JohnJohnJohnJohnJohn");
expect(result.ast?.toString()).toBe("JohnJohnJohnJohnJohn");
});
});