2022-03-24 12:41:31 +00:00
|
|
|
open Jest
|
|
|
|
open Reducer_TestHelpers
|
|
|
|
|
2022-04-05 18:54:51 +00:00
|
|
|
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
2022-04-05 17:36:19 +00:00
|
|
|
|
2022-04-12 23:59:40 +00:00
|
|
|
let testDescriptionParseToBe = (desc, expr, answer) =>
|
|
|
|
test(desc, () => expectParseToBe(expr, answer))
|
2022-04-05 17:18:10 +00:00
|
|
|
|
2022-04-05 18:54:51 +00:00
|
|
|
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
2022-04-05 17:36:19 +00:00
|
|
|
|
2022-04-12 14:19:50 +00:00
|
|
|
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
2022-04-05 17:36:19 +00:00
|
|
|
|
2022-04-15 06:56:48 +00:00
|
|
|
let testEvalBindingsToBe = (expr, bindings, answer) =>
|
|
|
|
test(expr, () => expectEvalBindingsToBe(expr, bindings, answer))
|
|
|
|
|
2022-03-24 12:41:31 +00:00
|
|
|
describe("reducer using mathjs parse", () => {
|
|
|
|
// Test the MathJs parser compatibility
|
2022-03-30 10:53:36 +00:00
|
|
|
// Those tests toString that there is a semantic mapping from MathJs to Expression
|
2022-03-24 12:41:31 +00:00
|
|
|
// Reducer.parse is called by Reducer.eval
|
|
|
|
// See https://mathjs.org/docs/expressions/syntax.html
|
|
|
|
// See https://mathjs.org/docs/reference/functions.html
|
2022-03-30 10:53:36 +00:00
|
|
|
// Those tests toString that we are converting mathjs parse tree to what we need
|
2022-03-24 12:41:31 +00:00
|
|
|
|
|
|
|
describe("expressions", () => {
|
2022-04-05 17:18:10 +00:00
|
|
|
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)))")
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
|
|
|
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)
|
2022-04-12 14:19:50 +00:00
|
|
|
testDescriptionParseToBe("empty", "[]", "Ok(())")
|
2022-04-05 17:18:10 +00:00
|
|
|
testParseToBe("[1, 2, 3]", "Ok((1 2 3))")
|
|
|
|
testParseToBe("['hello', 'world']", "Ok(('hello' 'world'))")
|
2022-04-12 14:19:50 +00:00
|
|
|
testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$atIndex (0 1 2) (1)))")
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
|
|
|
describe("records", () => {
|
2022-04-12 14:19:50 +00:00
|
|
|
testDescriptionParseToBe("define", "{a: 1, b: 2}", "Ok((:$constructRecord (('a' 1) ('b' 2))))")
|
|
|
|
testDescriptionParseToBe(
|
2022-04-05 18:54:51 +00:00
|
|
|
"use",
|
|
|
|
"{a: 1, b: 2}.a",
|
|
|
|
"Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))",
|
|
|
|
)
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
2022-04-08 12:14:37 +00:00
|
|
|
describe("multi-line", () => {
|
|
|
|
testParseToBe("1; 2", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) 1) 2))")
|
2022-04-12 23:59:40 +00:00
|
|
|
testParseToBe(
|
|
|
|
"1+1; 2+1",
|
|
|
|
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:add 1 1)) (:add 2 1)))",
|
|
|
|
)
|
2022-04-08 12:14:37 +00:00
|
|
|
})
|
|
|
|
describe("assignment", () => {
|
2022-04-12 23:59:40 +00:00
|
|
|
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)))",
|
|
|
|
)
|
2022-04-08 12:14:37 +00:00
|
|
|
})
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
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", () => {
|
2022-04-05 17:18:10 +00:00
|
|
|
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)")
|
2022-03-24 12:41:31 +00:00
|
|
|
// TODO more built ins
|
|
|
|
})
|
|
|
|
describe("arrays", () => {
|
2022-03-29 09:09:59 +00:00
|
|
|
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
|
2022-04-05 17:18:10 +00:00
|
|
|
testEvalToBe("[1, 2, 3]", "Ok([1, 2, 3])")
|
|
|
|
testEvalToBe("['hello', 'world']", "Ok(['hello', 'world'])")
|
|
|
|
testEvalToBe("([0,1,2])[1]", "Ok(1)")
|
2022-04-12 14:19:50 +00:00
|
|
|
testDescriptionEvalToBe("index not found", "([0,1,2])[10]", "Error(Array index not found: 10)")
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
|
|
|
describe("records", () => {
|
2022-03-29 09:09:59 +00:00
|
|
|
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)"))
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
2022-04-08 12:14:37 +00:00
|
|
|
|
|
|
|
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)")
|
|
|
|
})
|
2022-04-15 06:56:48 +00:00
|
|
|
describe("external bindings", () => {
|
|
|
|
testEvalBindingsToBe(
|
|
|
|
"y=1; x+1",
|
|
|
|
list{("x", ExpressionValue.EvNumber(1.))}->Js.Dict.fromList,
|
|
|
|
"Ok(2)",
|
|
|
|
)
|
|
|
|
testEvalBindingsToBe(
|
|
|
|
// This will go away when we have a proper parser
|
|
|
|
// x+1 is an expression not a block!
|
|
|
|
// Bindings are done for blocks only
|
|
|
|
"x+1",
|
|
|
|
list{("x", ExpressionValue.EvNumber(1.))}->Js.Dict.fromList,
|
|
|
|
"Error(JS Exception: Error: Undefined symbol x)",
|
|
|
|
)
|
2022-04-15 11:49:04 +00:00
|
|
|
testEvalToBe(
|
|
|
|
"x=1; y=1",
|
|
|
|
"???",
|
|
|
|
)
|
2022-04-15 06:56:48 +00:00
|
|
|
})
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
describe("test exceptions", () => {
|
2022-04-12 14:19:50 +00:00
|
|
|
testDescriptionEvalToBe(
|
2022-04-05 18:54:51 +00:00
|
|
|
"javascript exception",
|
2022-04-08 12:14:37 +00:00
|
|
|
"javascriptraise('div by 0')",
|
2022-04-05 18:54:51 +00:00
|
|
|
"Error(JS Exception: Error: 'div by 0')",
|
|
|
|
)
|
2022-04-12 23:59:40 +00:00
|
|
|
testDescriptionEvalToBe(
|
|
|
|
"rescript exception",
|
|
|
|
"rescriptraise()",
|
|
|
|
"Error(TODO: unhandled rescript exception)",
|
|
|
|
)
|
2022-03-24 12:41:31 +00:00
|
|
|
})
|