UNPKG

clarity-pattern-parser

Version:

Parsing Library for Typescript and Javascript.

208 lines (166 loc) 7.01 kB
import { Cursor } from "./Cursor"; import { Literal } from "./Literal"; import { Options } from "./Options"; import { Expression } from "./Expression"; import { Reference } from "./Reference"; import { Sequence } from "./Sequence"; import { RightAssociated } from "./RightAssociated"; function createTernaryExpression() { const variables = new Options("variables", [ new Literal("a", "a"), new Literal("b", "b"), new Literal("c", "c"), ]); const addOp = new Literal("add", " + "); const addExpression = new Sequence("add-expression", [ new Reference("expression"), addOp, new Reference("expression"), ]); const ternary = new Sequence("ternary", [ new Reference("expression"), new Literal("question-mark", " ? "), new Reference("expression"), new Literal("colon", " : "), new Reference("expression"), ]); const expression = new Expression("expression", [ addExpression, new RightAssociated(ternary), variables, ]); return expression; } describe("RightAssociated", () => { test("metadata: type and name", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); expect(ra.type).toBe("right-associated"); expect(ra.name).toBe(""); expect(ra.id).toMatch(/^right-associated-/); }); test("wraps a child pattern via clone", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); // The child should be a clone, not the same reference expect(ra.children).toHaveLength(1); expect(ra.children[0]).not.toBe(inner); expect(ra.children[0].type).toBe("literal"); expect(ra.children[0].name).toBe("x"); }); test("parse delegates to child pattern", () => { const inner = new Literal("greeting", "hello"); const ra = new RightAssociated(inner); const cursor = new Cursor("hello"); const node = ra.parse(cursor); expect(node?.value).toBe("hello"); expect(node?.name).toBe("greeting"); expect(ra.startedOnIndex).toBe(0); }); test("test delegates to child pattern", () => { const inner = new Literal("greeting", "hello"); const ra = new RightAssociated(inner); expect(ra.test("hello")).toBe(true); expect(ra.test("world")).toBe(false); }); test("exec delegates to child pattern on failure", () => { const inner = new Literal("greeting", "hello"); const ra = new RightAssociated(inner); const result = ra.exec("world"); expect(result.ast).toBeNull(); }); test("clone creates an independent copy", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); const cloned = ra.clone(); expect(cloned).not.toBe(ra); expect(cloned.type).toBe("right-associated"); expect(cloned.id).toBe(ra.id); expect(cloned.children).toHaveLength(1); expect(cloned.children[0]).not.toBe(ra.children[0]); }); test("getTokens delegates to child", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); // Literal's getTokens returns the literal value const tokens = ra.getTokens(); expect(tokens).toEqual(["x"]); }); test("getPatterns delegates to child", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); const patterns = ra.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].name).toBe("x"); }); test("find searches within child", () => { const a = new Literal("a", "a"); const b = new Literal("b", "b"); const seq = new Sequence("ab", [a, b]); const ra = new RightAssociated(seq); const found = ra.find(p => p.name === "b"); expect(found).not.toBeNull(); expect(found!.name).toBe("b"); const notFound = ra.find(p => p.name === "z"); expect(notFound).toBeNull(); }); test("isEqual compares structure", () => { const innerA = new Literal("x", "x"); const raA = new RightAssociated(innerA); const innerB = new Literal("x", "x"); const raB = new RightAssociated(innerB); expect(raA.isEqual(raB)).toBe(true); const innerC = new Literal("y", "y"); const raC = new RightAssociated(innerC); expect(raA.isEqual(raC)).toBe(false); }); test("standalone getNextTokens/getNextPatterns return empty (no parent)", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); expect(ra.getNextTokens()).toEqual([]); expect(ra.getNextPatterns()).toEqual([]); }); test("getTokensAfter and getPatternsAfter return empty when no parent", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); expect(ra.getTokensAfter(inner)).toEqual([]); expect(ra.getPatternsAfter(inner)).toEqual([]); }); test("introspection delegates to parent when parent exists", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); const semi = new Literal("semi", ";"); const seq = new Sequence("stmt", [ra, semi]); const raInSeq = seq.children[0]; expect(raInSeq.getNextTokens()).toEqual([";"]); expect(raInSeq.getNextPatterns().length).toBe(1); expect(raInSeq.getTokensAfter(raInSeq.children[0])).toEqual([";"]); expect(raInSeq.getPatternsAfter(raInSeq.children[0]).length).toBe(1); }); test("parent getter and setter", () => { const inner = new Literal("x", "x"); const ra = new RightAssociated(inner); expect(ra.parent).toBeNull(); const fakeParent = new Literal("parent", "p"); ra.parent = fakeParent; expect(ra.parent).toBe(fakeParent); }); test("ternary expression uses right-association in an Expression", () => { const expression = createTernaryExpression(); // Ternary is right-associative: a ? b : c should parse correctly const result = expression.exec("a ? b : c"); expect(result.ast).not.toBeNull(); expect(result.ast?.value).toBe("a ? b : c"); // Nested ternary: right-association means a ? b : c ? a : b // groups as a ? b : (c ? a : b) const nested = expression.exec("a ? b : c ? a : b"); expect(nested.ast).not.toBeNull(); expect(nested.ast?.value).toBe("a ? b : c ? a : b"); }); test("startedOnIndex delegates to child", () => { const expression = createTernaryExpression(); // After parsing, startedOnIndex should reflect the child const result = expression.exec("a + b"); expect(result.ast).not.toBeNull(); }); });