import { CaretParser, Strand, CharToken, EditorState, MissingNode, TokensTag, UnparseableNode, } from "@caret-js/core"; import { expect, test } from "vitest"; import { AddNode, DecimalNode, defaultParselets, DivideNode, EquationNode, ExponentNode, FractionToken, FunctionCallNode, MultiplyNode, NegativeNode, ParenthesesToken, PositiveNode, SubSupToken, SubtractNode, VariableNode, } from "."; import { ParenthesesChildTag } from "./parselets/parentheses"; import { MultiplicationStyleTag } from "./parselets/adjacentMultiplication"; test("Parse 3.14", () => { const three = new CharToken("3"); const point = new CharToken("."); const one = new CharToken("1"); const four = new CharToken("4"); const editorState = new EditorState(new Strand([three, point, one, four])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( DecimalNode.from("3.14").addTag(new TokensTag([three, point, one, four])) ); }); test("Parse x", () => { const x = new CharToken("x"); const editorState = new EditorState(new Strand([x])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( VariableNode.from("x").addTag(new TokensTag([x])) ); }); test("Parse x+1", () => { const x = new CharToken("x"); const plus = new CharToken("+"); const one = new CharToken("1"); const editorState = new EditorState(new Strand([x, plus, one])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( AddNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), DecimalNode.from("1").addTag(new TokensTag([one])), ]).addTag(new TokensTag([plus])) ); }); test("Parse 3x", () => { const three = new CharToken("3"); const x = new CharToken("x"); const editorState = new EditorState(new Strand([three, x])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ DecimalNode.from("3").addTag(new TokensTag([three])), VariableNode.from("x").addTag(new TokensTag([x])), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse -x", () => { const negative = new CharToken("-"); const x = new CharToken("x"); const editorState = new EditorState(new Strand([negative, x])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( NegativeNode.from(VariableNode.from("x").addTag(new TokensTag([x]))).addTag( new TokensTag([negative]) ) ); }); test("Parse +x", () => { const plus = new CharToken("+"); const x = new CharToken("x"); const editorState = new EditorState(new Strand([plus, x])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( PositiveNode.from(VariableNode.from("x").addTag(new TokensTag([x]))).addTag( new TokensTag([plus]) ) ); }); test("Parse -3.14", () => { const negative = new CharToken("-"); const three = new CharToken("3"); const point = new CharToken("."); const one = new CharToken("1"); const four = new CharToken("4"); const editorState = new EditorState( new Strand([negative, three, point, one, four]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( DecimalNode.from("-3.14").addTag( new TokensTag([negative, three, point, one, four]) ) ); }); test("Parse +3.14", () => { const plus = new CharToken("+"); const three = new CharToken("3"); const point = new CharToken("."); const one = new CharToken("1"); const four = new CharToken("4"); const editorState = new EditorState( new Strand([plus, three, point, one, four]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( PositiveNode.from( DecimalNode.from("3.14").addTag(new TokensTag([three, point, one, four])) ).addTag(new TokensTag([plus])) ); }); test("Parse --2", () => { const minus1 = new CharToken("-"); const minus2 = new CharToken("-"); const two = new CharToken("2"); const editorState = new EditorState(new Strand([minus1, minus2, two])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( NegativeNode.from( DecimalNode.from("-2").addTag(new TokensTag([minus2, two])) ).addTag(new TokensTag([minus1])) ); }); test("Parse 1-2+3", () => { const one = new CharToken("1"); const minus = new CharToken("-"); const two = new CharToken("2"); const plus = new CharToken("+"); const three = new CharToken("3"); const editorState = new EditorState( new Strand([one, minus, two, plus, three]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( AddNode.from([ SubtractNode.from( DecimalNode.from("1").addTag(new TokensTag([one])), DecimalNode.from("2").addTag(new TokensTag([two])) ).addTag(new TokensTag([minus])), DecimalNode.from("3").addTag(new TokensTag([three])), ]).addTag(new TokensTag([plus])) ); }); test("Parse a(b+1)", () => { const a = new CharToken("a"); const b = new CharToken("b"); const plus = new CharToken("+"); const one = new CharToken("1"); const parens = new ParenthesesToken(new Strand([b, plus, one])); const editorState = new EditorState(new Strand([a, parens])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ new VariableNode("a").addTag(new TokensTag([a])), new AddNode([ new VariableNode("b").addTag(new TokensTag([b])), new DecimalNode("1").addTag(new TokensTag([one])), ]) .addTag(new TokensTag([plus, parens])) .addTag(new ParenthesesChildTag()), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse f(x)", () => { const f = new CharToken("f"); const x = new CharToken("x"); const parens = new ParenthesesToken(new Strand([x])); const editorState = new EditorState(new Strand([f, parens])); const parser = new CaretParser( editorState, [], [...defaultParselets({ funcNames: new Set(["f"]) })] ); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( FunctionCallNode.from("f", [ VariableNode.from("x") .addTag(new TokensTag([x, parens])) .addTag(new ParenthesesChildTag()), ]).addTag(new TokensTag([f])) ); }); test("Parse f(x+1)", () => { const f = new CharToken("f"); const x = new CharToken("x"); const plus = new CharToken("+"); const one = new CharToken("1"); const parens = new ParenthesesToken(new Strand([x, plus, one])); const editorState = new EditorState(new Strand([f, parens])); const parser = new CaretParser( editorState, [], [...defaultParselets({ funcNames: new Set(["f"]) })] ); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( FunctionCallNode.from("f", [ AddNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), DecimalNode.from("1").addTag(new TokensTag([one])), ]) .addTag(new TokensTag([plus, parens])) .addTag(new ParenthesesChildTag()), ]).addTag(new TokensTag([f])) ); }); test("Parse xf(x)", () => { const x1 = new CharToken("x"); const f = new CharToken("f"); const x2 = new CharToken("x"); const parens = new ParenthesesToken(new Strand([x2])); const editorState = new EditorState(new Strand([x1, f, parens])); const parser = new CaretParser( editorState, [], [...defaultParselets({ funcNames: new Set(["f"]) })] ); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ VariableNode.from("x").addTag(new TokensTag([x1])), FunctionCallNode.from("f", [ VariableNode.from("x") .addTag(new TokensTag([x2, parens])) .addTag(new ParenthesesChildTag()), ]).addTag(new TokensTag([f])), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse x+", () => { const x = new CharToken("x"); const plus = new CharToken("+"); const editorState = new EditorState(new Strand([x, plus])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( AddNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), MissingNode.from(), ]).addTag(new TokensTag([plus])) ); }); test("Parse x-", () => { const x = new CharToken("x"); const minus = new CharToken("-"); const editorState = new EditorState(new Strand([x, minus])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( SubtractNode.from( VariableNode.from("x").addTag(new TokensTag([x])), MissingNode.from() ).addTag(new TokensTag([minus])) ); }); test("Parse x^2y^2", () => { const x = new CharToken("x"); const two1 = new CharToken("2"); const subsup1 = new SubSupToken(null, new Strand([two1])); const y = new CharToken("y"); const two2 = new CharToken("2"); const subsup2 = new SubSupToken(null, new Strand([two2])); const editorState = new EditorState(new Strand([x, subsup1, y, subsup2])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ ExponentNode.from( VariableNode.from("x").addTag(new TokensTag([x])), DecimalNode.from("2").addTag(new TokensTag([two1])) ).addTag(new TokensTag([subsup1])), ExponentNode.from( VariableNode.from("y").addTag(new TokensTag([y])), DecimalNode.from("2").addTag(new TokensTag([two2])) ).addTag(new TokensTag([subsup2])), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse 2x+3y", () => { const two = new CharToken("2"); const x = new CharToken("x"); const plus = new CharToken("+"); const three = new CharToken("3"); const y = new CharToken("y"); const editorState = new EditorState(new Strand([two, x, plus, three, y])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( AddNode.from([ MultiplyNode.from([ DecimalNode.from("2").addTag(new TokensTag([two])), VariableNode.from("x").addTag(new TokensTag([x])), ]).addTag(new MultiplicationStyleTag("implicit")), MultiplyNode.from([ DecimalNode.from("3").addTag(new TokensTag([three])), VariableNode.from("y").addTag(new TokensTag([y])), ]).addTag(new MultiplicationStyleTag("implicit")), ]).addTag(new TokensTag([plus])) ); }); test("Parse y=1/2x+3", () => { const y = new CharToken("y"); const equals = new CharToken("="); const one = new CharToken("1"); const two = new CharToken("2"); const fraction = new FractionToken(new Strand([one]), new Strand([two])); const x = new CharToken("x"); const plus = new CharToken("+"); const three = new CharToken("3"); const editorState = new EditorState( new Strand([y, equals, fraction, x, plus, three]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( EquationNode.from([ VariableNode.from("y").addTag(new TokensTag([y])), AddNode.from([ MultiplyNode.from([ DivideNode.from( DecimalNode.from("1").addTag(new TokensTag([one])), DecimalNode.from("2").addTag(new TokensTag([two])) ).addTag(new TokensTag([fraction])), VariableNode.from("x").addTag(new TokensTag([x])), ]).addTag(new MultiplicationStyleTag("implicit")), DecimalNode.from("3").addTag(new TokensTag([three])), ]).addTag(new TokensTag([plus])), ]).addTag(new TokensTag([equals])) ); }); test("Parse (2x+-1)(x-3)", () => { const two = new CharToken("2"); const x1 = new CharToken("x"); const plus = new CharToken("+"); const negative = new CharToken("-"); const one = new CharToken("1"); const parens1 = new ParenthesesToken( new Strand([two, x1, plus, negative, one]) ); const x2 = new CharToken("x"); const minus = new CharToken("-"); const three = new CharToken("3"); const parens2 = new ParenthesesToken(new Strand([x2, minus, three])); const editorState = new EditorState(new Strand([parens1, parens2])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ AddNode.from([ MultiplyNode.from([ DecimalNode.from("2").addTag(new TokensTag([two])), VariableNode.from("x").addTag(new TokensTag([x1])), ]).addTag(new MultiplicationStyleTag("implicit")), DecimalNode.from("-1").addTag(new TokensTag([negative, one])), ]) .addTag(new TokensTag([plus, parens1])) .addTag(new ParenthesesChildTag()), SubtractNode.from( VariableNode.from("x").addTag(new TokensTag([x2])), DecimalNode.from("3").addTag(new TokensTag([three])) ) .addTag(new TokensTag([minus, parens2])) .addTag(new ParenthesesChildTag()), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse -x^2", () => { const negative = new CharToken("-"); const x = new CharToken("x"); const two = new CharToken("2"); const subsup = new SubSupToken(null, new Strand([two])); const editorState = new EditorState(new Strand([negative, x, subsup])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( NegativeNode.from( ExponentNode.from( VariableNode.from("x").addTag(new TokensTag([x])), DecimalNode.from("2").addTag(new TokensTag([two])) ).addTag(new TokensTag([subsup])) ).addTag(new TokensTag([negative])) ); }); test("Parse x*y-z", () => { /* This currently fails because on the right-hand side of the * we are restarting parsing to get the y. We want to continue parsing anything that binds stronger or the same as *, which includes implicit multiplication. As a result, the "y-z" is being parsed as Multiply(y, Negative(z)). This does NOT happen if you parse "y-z" alone. The difference is that we are parsing with a high precedence where Subtract(y, z) is not allowed but Multiply(y, Negative(z)) is. */ const x = new CharToken("x"); const multiply = new CharToken("*"); const y = new CharToken("y"); const subtract = new CharToken("-"); const z = new CharToken("z"); const editorState = new EditorState( new Strand([x, multiply, y, subtract, z]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( SubtractNode.from( MultiplyNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), VariableNode.from("y").addTag(new TokensTag([y])), ]) .addTag(new TokensTag([multiply])) .addTag(new MultiplicationStyleTag("dot")), VariableNode.from("z").addTag(new TokensTag([z])) ).addTag(new TokensTag([subtract])) ); }); test("Parse 1.2.3", () => { const one = new CharToken("1"); const point1 = new CharToken("."); const two = new CharToken("2"); const point2 = new CharToken("."); const three = new CharToken("3"); const editorState = new EditorState( new Strand([one, point1, two, point2, three]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( UnparseableNode.from( DecimalNode.from("1.2").addTag(new TokensTag([one, point1, two])), [point2, three] ) ); }); test("Parse 1.2.", () => { const one = new CharToken("1"); const point1 = new CharToken("."); const two = new CharToken("2"); const point2 = new CharToken("."); const editorState = new EditorState(new Strand([one, point1, two, point2])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( UnparseableNode.from( DecimalNode.from("1.2").addTag(new TokensTag([one, point1, two])), [point2] ) ); }); test("Parse -.", () => { const negative = new CharToken("-"); const point = new CharToken("."); const editorState = new EditorState(new Strand([negative, point])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( NegativeNode.from(new UnparseableNode(null, [point])).addTag( new TokensTag([negative]) ) ); }); test("Parse 1()", () => { const one = new CharToken("1"); const parens = new ParenthesesToken(new Strand([])); const editorState = new EditorState(new Strand([one, parens])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( MultiplyNode.from([ DecimalNode.from("1").addTag(new TokensTag([one])), new MissingNode() .addTag(new TokensTag([parens])) .addTag(new ParenthesesChildTag()), ]).addTag(new MultiplicationStyleTag("implicit")) ); }); test("Parse +--++-+--3", () => { const plus1 = new CharToken("+"); const minus1 = new CharToken("-"); const minus2 = new CharToken("-"); const plus2 = new CharToken("+"); const plus3 = new CharToken("+"); const minus3 = new CharToken("-"); const plus4 = new CharToken("+"); const minus4 = new CharToken("-"); const minus5 = new CharToken("-"); const three = new CharToken("3"); const editorState = new EditorState( new Strand([ plus1, minus1, minus2, plus2, plus3, minus3, plus4, minus4, minus5, three, ]) ); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( PositiveNode.from( NegativeNode.from( NegativeNode.from( PositiveNode.from( PositiveNode.from( NegativeNode.from( PositiveNode.from( NegativeNode.from( DecimalNode.from("-3").addTag( new TokensTag([minus5, three]) ) ).addTag(new TokensTag([minus4])) ).addTag(new TokensTag([plus4])) ).addTag(new TokensTag([minus3])) ).addTag(new TokensTag([plus3])) ).addTag(new TokensTag([plus2])) ).addTag(new TokensTag([minus2])) ).addTag(new TokensTag([minus1])) ).addTag(new TokensTag([plus1])) ); }); test("Parse ab-cd", () => { const a = new CharToken("a"); const b = new CharToken("b"); const minus = new CharToken("-"); const c = new CharToken("c"); const d = new CharToken("d"); const editorState = new EditorState(new Strand([a, b, minus, c, d])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( SubtractNode.from( MultiplyNode.from([ VariableNode.from("a").addTag(new TokensTag([a])), VariableNode.from("b").addTag(new TokensTag([b])), ]).addTag(new MultiplicationStyleTag("implicit")), MultiplyNode.from([ VariableNode.from("c").addTag(new TokensTag([c])), VariableNode.from("d").addTag(new TokensTag([d])), ]).addTag(new MultiplicationStyleTag("implicit")) ).addTag(new TokensTag([minus])) ); }); test("Parse 1x-1y", () => { const one1 = new CharToken("1"); const x = new CharToken("x"); const minus = new CharToken("-"); const one2 = new CharToken("1"); const y = new CharToken("y"); const editorState = new EditorState(new Strand([one1, x, minus, one2, y])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( SubtractNode.from( MultiplyNode.from([ DecimalNode.from("1").addTag(new TokensTag([one1])), VariableNode.from("x").addTag(new TokensTag([x])), ]).addTag(new MultiplicationStyleTag("implicit")), MultiplyNode.from([ DecimalNode.from("1").addTag(new TokensTag([one2])), VariableNode.from("y").addTag(new TokensTag([y])), ]).addTag(new MultiplicationStyleTag("implicit")) ).addTag(new TokensTag([minus])) ); }); test("Parse x1y", () => { const x = new CharToken("x"); const one = new CharToken("1"); const y = new CharToken("y"); const editorState = new EditorState(new Strand([x, one, y])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( UnparseableNode.from(VariableNode.from("x").addTag(new TokensTag([x])), [ one, y, ]) ); }); test.skip("Parse xy1", () => { const x = new CharToken("x"); const y = new CharToken("y"); const one = new CharToken("1"); const editorState = new EditorState(new Strand([x, y, one])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( UnparseableNode.from( MultiplyNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), VariableNode.from("y").addTag(new TokensTag([y])), ]).addTag(new MultiplicationStyleTag("implicit")), [one] ) ); }); test.skip("Parse x+y1", () => { const x = new CharToken("x"); const plus = new CharToken("+"); const y = new CharToken("y"); const one = new CharToken("1"); const editorState = new EditorState(new Strand([x, plus, y, one])); const parser = new CaretParser(editorState, [], [...defaultParselets()]); const parseResult = parser.parse(); expect(parseResult).toStrictEqual( UnparseableNode.from( AddNode.from([ VariableNode.from("x").addTag(new TokensTag([x])), VariableNode.from("y").addTag(new TokensTag([y])), ]).addTag(new TokensTag([plus])), [one] ) ); });