open Jest open Reducer_TestHelpers let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) let testDescParseToBe = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer)) let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) let testDescEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer)) describe("reducer using mathjs parse", () => { // Test the MathJs parser compatibility // Those tests toString that there is a semantic mapping from MathJs to Expression // Reducer.parse is called by Reducer.eval // See https://mathjs.org/docs/expressions/syntax.html // See https://mathjs.org/docs/reference/functions.html // Those tests toString that we are converting mathjs parse tree to what we need describe("expressions", () => { testParseToBe("1", "Ok(1)") testParseToBe("(1)", "Ok(1)") testParseToBe("1+2", "Ok((:add 1 2))") testParseToBe("1+2", "Ok((:add 1 2))") testParseToBe("1+2", "Ok((:add 1 2))") testParseToBe("1+2*3", "Ok((:add 1 (:multiply 2 3)))") }) describe("arrays", () => { //Note. () is a empty list in Lisp // The only builtin structure in Lisp is list. There are no arrays // [1,2,3] becomes (1 2 3) testDescParseToBe("empty", "[]", "Ok(())") testParseToBe("[1, 2, 3]", "Ok((1 2 3))") testParseToBe("['hello', 'world']", "Ok(('hello' 'world'))") testDescParseToBe("index", "([0,1,2])[1]", "Ok((:$atIndex (0 1 2) (1)))") }) describe("records", () => { testDescParseToBe("define", "{a: 1, b: 2}", "Ok((:$constructRecord (('a' 1) ('b' 2))))") testDescParseToBe( "use", "{a: 1, b: 2}.a", "Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))", ) }) describe("multi-line", () => { testParseToBe("1; 2", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) 1) 2))") testParseToBe("1+1; 2+1", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:add 1 1)) (:add 2 1)))") }) describe("assignment", () => { testParseToBe("x=1; x", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x 1)) :x))") testParseToBe("x=1+1; x+1", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x (:add 1 1))) (:add :x 1)))") }) }) describe("eval", () => { // All MathJs operators and functions are builtin for string, float and boolean // .e.g + - / * > >= < <= == /= not and or // See https://mathjs.org/docs/expressions/syntax.html // See https://mathjs.org/docs/reference/functions.html describe("expressions", () => { testEvalToBe("1", "Ok(1)") testEvalToBe("1+2", "Ok(3)") testEvalToBe("(1+2)*3", "Ok(9)") testEvalToBe("2>1", "Ok(true)") testEvalToBe("concat('a ', 'b')", "Ok('a b')") testEvalToBe("log(10)", "Ok(2.302585092994046)") testEvalToBe("cos(10)", "Ok(-0.8390715290764524)") // TODO more built ins }) describe("arrays", () => { test("empty array", () => expectEvalToBe("[]", "Ok([])")) testEvalToBe("[1, 2, 3]", "Ok([1, 2, 3])") testEvalToBe("['hello', 'world']", "Ok(['hello', 'world'])") testEvalToBe("([0,1,2])[1]", "Ok(1)") testDescEvalToBe("index not found", "([0,1,2])[10]", "Error(Array index not found: 10)") }) describe("records", () => { test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1, b: 2})")) test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)")) test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)")) }) describe("multi-line", () => { testEvalToBe("1; 2", "Error(Assignment expected)") testEvalToBe("1+1; 2+1", "Error(Assignment expected)") }) describe("assignment", () => { testEvalToBe("x=1; x", "Ok(1)") testEvalToBe("x=1+1; x+1", "Ok(3)") testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") testEvalToBe("1; x=1", "Error(Assignment expected)") testEvalToBe("1; 1", "Error(Assignment expected)") testEvalToBe("x=1; x=1", "Error(Expression expected)") }) }) describe("test exceptions", () => { testDescEvalToBe( "javascript exception", "javascriptraise('div by 0')", "Error(JS Exception: Error: 'div by 0')", ) testDescEvalToBe("rescript exception", "rescriptraise()", "Error(TODO: unhandled rescript exception)") })