Merge branch 'peggy' into reducer-dev
This commit is contained in:
commit
915b49bf97
|
@ -16,33 +16,33 @@ testMacro([], exampleExpression, "Ok(1)")
|
||||||
|
|
||||||
describe("bindStatement", () => {
|
describe("bindStatement", () => {
|
||||||
// A statement is bound by the bindings created by the previous statement
|
// A statement is bound by the bindings created by the previous statement
|
||||||
testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1))")
|
testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1) context: {})")
|
||||||
// Then it answers the bindings for the next statement when reduced
|
// Then it answers the bindings for the next statement when reduced
|
||||||
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
|
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
|
||||||
// Now let's feed a binding to see what happens
|
// Now let's feed a binding to see what happens
|
||||||
testMacro(
|
testMacro(
|
||||||
[],
|
[],
|
||||||
eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX),
|
eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX),
|
||||||
"Ok((:$setBindings {x: 2} :y 2))",
|
"Ok((:$setBindings {x: 2} :y 2) context: {x: 2})",
|
||||||
)
|
)
|
||||||
// An expression does not return a binding, thus error
|
// An expression does not return a binding, thus error
|
||||||
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Error(Assignment expected)")
|
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
|
||||||
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
|
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
|
||||||
testMacro(
|
testMacro(
|
||||||
[("z", EvNumber(99.))],
|
[("z", EvNumber(99.))],
|
||||||
eBindStatementDefault(exampleStatementY),
|
eBindStatementDefault(exampleStatementY),
|
||||||
"Ok((:$setBindings {z: 99} :y 1))",
|
"Ok((:$setBindings {z: 99} :y 1) context: {z: 99})",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("bindExpression", () => {
|
describe("bindExpression", () => {
|
||||||
// x is simply bound in the expression
|
// x is simply bound in the expression
|
||||||
testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2)")
|
testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2 context: {x: 2})")
|
||||||
// When an let statement is the end expression then bindings are returned
|
// When an let statement is the end expression then bindings are returned
|
||||||
testMacro(
|
testMacro(
|
||||||
[],
|
[],
|
||||||
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY),
|
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY),
|
||||||
"Ok((:$exportBindings (:$setBindings {x: 2} :y 1)))",
|
"Ok((:$exportBindings (:$setBindings {x: 2} :y 1)) context: {x: 2})",
|
||||||
)
|
)
|
||||||
// Now let's reduce that expression
|
// Now let's reduce that expression
|
||||||
testMacroEval(
|
testMacroEval(
|
||||||
|
@ -110,7 +110,7 @@ describe("block", () => {
|
||||||
}),
|
}),
|
||||||
"Ok((:$$bindExpression (:$$block (:$let :y (:add :x 1)) :y)))",
|
"Ok((:$$bindExpression (:$$block (:$let :y (:add :x 1)) :y)))",
|
||||||
)
|
)
|
||||||
MyOnly.testMacroEval(
|
testMacroEval(
|
||||||
[("x", EvNumber(1.))],
|
[("x", EvNumber(1.))],
|
||||||
eBlock(list{
|
eBlock(list{
|
||||||
eBlock(list{
|
eBlock(list{
|
||||||
|
@ -125,7 +125,7 @@ describe("block", () => {
|
||||||
describe("lambda", () => {
|
describe("lambda", () => {
|
||||||
// assign a lambda to a variable
|
// assign a lambda to a variable
|
||||||
let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY})
|
let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY})
|
||||||
testMacro([], lambdaExpression, "Ok(lambda(y=>internal))")
|
testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
|
||||||
// call a lambda
|
// call a lambda
|
||||||
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
|
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
|
||||||
testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))")
|
testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))")
|
||||||
|
|
|
@ -22,6 +22,6 @@ describe("builtin", () => {
|
||||||
describe("builtin exception", () => {
|
describe("builtin exception", () => {
|
||||||
//It's a pity that MathJs does not return error position
|
//It's a pity that MathJs does not return error position
|
||||||
test("MathJs Exception", () =>
|
test("MathJs Exception", () =>
|
||||||
expectEvalToBe("testZadanga()", "Error(JS Exception: Error: Undefined function testZadanga)")
|
expectEvalToBe("testZadanga(1)", "Error(JS Exception: Error: Undefined function testZadanga)")
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
module Parse = Reducer_MathJs.Parse
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let expectParseToBe = (expr, answer) =>
|
|
||||||
Parse.parse(expr)->Result.flatMap(Parse.castNodeType)->Parse.toStringResult->expect->toBe(answer)
|
|
||||||
|
|
||||||
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
let testDescriptionParse = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
module MySkip = {
|
|
||||||
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Skip.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
module MyOnly = {
|
|
||||||
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Only.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("MathJs parse", () => {
|
|
||||||
describe("literals operators parenthesis", () => {
|
|
||||||
testParse("1", "1")
|
|
||||||
testParse("'hello'", "'hello'")
|
|
||||||
testParse("true", "true")
|
|
||||||
testParse("1+2", "add(1, 2)")
|
|
||||||
testParse("add(1,2)", "add(1, 2)")
|
|
||||||
testParse("(1)", "(1)")
|
|
||||||
testParse("(1+2)", "(add(1, 2))")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("multi-line", () => {
|
|
||||||
testParse("1; 2", "{1; 2}")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("variables", () => {
|
|
||||||
testParse("x = 1", "x = 1")
|
|
||||||
testParse("x", "x")
|
|
||||||
testParse("x = 1; x", "{x = 1; x}")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("functions", () => {
|
|
||||||
testParse("identity(x) = x", "identity = (x) => x")
|
|
||||||
testParse("identity(x)", "identity(x)")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("arrays", () => {
|
|
||||||
testDescriptionParse("empty", "[]", "[]")
|
|
||||||
testDescriptionParse("define", "[0, 1, 2]", "[0, 1, 2]")
|
|
||||||
testDescriptionParse("define with strings", "['hello', 'world']", "['hello', 'world']")
|
|
||||||
testParse("range(0, 4)", "range(0, 4)")
|
|
||||||
testDescriptionParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("records", () => {
|
|
||||||
testDescriptionParse("define", "{a: 1, b: 2}", "{a: 1, b: 2}")
|
|
||||||
testDescriptionParse("use", "record.property", "record['property']")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("comments", () => {
|
|
||||||
testDescriptionParse("define", "1 # This is a comment", "1")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("ternary operator", () => {
|
|
||||||
testParse("1 ? 2 : 3", "ternary(1, 2, 3)")
|
|
||||||
testParse("1 ? 2 : 3 ? 4 : 5", "ternary(1, 2, ternary(3, 4, 5))")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
module Parse = Reducer_Peggy_Parse
|
||||||
|
module Result = Belt.Result
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectParseToBe = (expr, answer) =>
|
||||||
|
Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
|
module MySkip = {
|
||||||
|
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
|
let testDescriptionParse = (desc, expr, answer) =>
|
||||||
|
Skip.test(desc, () => expectParseToBe(expr, answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
module MyOnly = {
|
||||||
|
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
let testDescriptionParse = (desc, expr, answer) =>
|
||||||
|
Only.test(desc, () => expectParseToBe(expr, answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Peggy parse", () => {
|
||||||
|
describe("literals operators parenthesis", () => {
|
||||||
|
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
|
||||||
|
testParse("1", "{1}")
|
||||||
|
testParse("'hello'", "{'hello'}")
|
||||||
|
testParse("true", "{true}")
|
||||||
|
testParse("1+2", "{(::add 1 2)}")
|
||||||
|
testParse("add(1,2)", "{(::add 1 2)}")
|
||||||
|
testParse("(1)", "{1}")
|
||||||
|
testParse("(1+2)", "{(::add 1 2)}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("unary", () => {
|
||||||
|
testParse("-1", "{(::unaryMinus 1)}")
|
||||||
|
testParse("!true", "{(::not true)}")
|
||||||
|
testParse("1 + -1", "{(::add 1 (::unaryMinus 1))}")
|
||||||
|
testParse("-a[0]", "{(::unaryMinus (::$atIndex :a 0))}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("multi-line", () => {
|
||||||
|
testParse("x=1; 2", "{:x = {1}; 2}")
|
||||||
|
testParse("x=1; y=2", "{:x = {1}; :y = {2}}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("variables", () => {
|
||||||
|
testParse("x = 1", "{:x = {1}}")
|
||||||
|
testParse("x", "{:x}")
|
||||||
|
testParse("x = 1; x", "{:x = {1}; :x}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("functions", () => {
|
||||||
|
testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments
|
||||||
|
testParse("identity(x)", "{(::identity :x)}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("arrays", () => {
|
||||||
|
testParse("[]", "{(::$constructArray ())}")
|
||||||
|
testParse("[0, 1, 2]", "{(::$constructArray (0 1 2))}")
|
||||||
|
testParse("['hello', 'world']", "{(::$constructArray ('hello' 'world'))}")
|
||||||
|
testParse("([0,1,2])[1]", "{(::$atIndex (::$constructArray (0 1 2)) 1)}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("records", () => {
|
||||||
|
testParse("{a: 1, b: 2}", "{(::$constructRecord ('a': 1 'b': 2))}")
|
||||||
|
testParse("{1+0: 1, 2+0: 2}", "{(::$constructRecord ((::add 1 0): 1 (::add 2 0): 2))}") // key can be any expression
|
||||||
|
testParse("record.property", "{(::$atIndex :record 'property')}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("comments", () => {
|
||||||
|
testParse("1 # This is a line comment", "{1}")
|
||||||
|
testParse("1 // This is a line comment", "{1}")
|
||||||
|
testParse("1 /* This is a multi line comment */", "{1}")
|
||||||
|
testParse("/* This is a multi line comment */ 1", "{1}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("ternary operator", () => {
|
||||||
|
testParse("1 ? 2 : 3", "{(::$$ternary 1 2 3)}")
|
||||||
|
testParse("1 ? 2 : 3 ? 4 : 5", "{(::$$ternary 1 2 (::$$ternary 3 4 5))}") // nested ternary
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("if then else", () => {
|
||||||
|
testParse("if 1 then 2 else 3", "{(::$$ternary 1 {2} {3})}")
|
||||||
|
testParse("if 1 then {2} else {3}", "{(::$$ternary 1 {2} {3})}")
|
||||||
|
testParse(
|
||||||
|
"if 1 then {2} else if 3 then {4} else {5}",
|
||||||
|
"{(::$$ternary 1 {2} (::$$ternary 3 {4} {5}))}",
|
||||||
|
) //nested if
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("pipe", () => {
|
||||||
|
testParse("1 -> add(2)", "{(::add 1 2)}")
|
||||||
|
testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}")
|
||||||
|
testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("elixir pipe", () => {
|
||||||
|
testParse("1 |> add(2)", "{(::add 1 2)}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("to", () => {
|
||||||
|
testParse("1 to 2", "{(::credibleIntervalToDistribution 1 2)}")
|
||||||
|
testParse("-1 to -2", "{(::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2))}") // lower than unary
|
||||||
|
testParse(
|
||||||
|
"a[1] to a[2]",
|
||||||
|
"{(::credibleIntervalToDistribution (::$atIndex :a 1) (::$atIndex :a 2))}",
|
||||||
|
) // lower than post
|
||||||
|
testParse(
|
||||||
|
"a.p1 to a.p2",
|
||||||
|
"{(::credibleIntervalToDistribution (::$atIndex :a 'p1') (::$atIndex :a 'p2'))}",
|
||||||
|
) // lower than post
|
||||||
|
testParse("1 to 2 + 3", "{(::add (::credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators
|
||||||
|
testParse(
|
||||||
|
"1->add(2) to 3->add(4) -> add(4)",
|
||||||
|
"{(::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4))}",
|
||||||
|
) // lower than chain
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("inner block", () => {
|
||||||
|
// inner blocks are 0 argument lambdas. They can be used whenever a value is required.
|
||||||
|
// Like lambdas they have a local scope.
|
||||||
|
testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; :x}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("lambda", () => {
|
||||||
|
testParse("{|x| x}", "{{|:x| {:x}}}")
|
||||||
|
testParse("f={|x| x}", "{:f = {{|:x| {:x}}}}")
|
||||||
|
testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments
|
||||||
|
testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$ternary :x 1 0)}}}") // Function definitions are lambda assignments
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Using lambda as value", () => {
|
||||||
|
testParse("myadd(x,y)=x+y; z=myadd; z", "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}")
|
||||||
|
testParse("myadd(x,y)=x+y; z=[myadd]; z", "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructArray (:myadd))}; :z}")
|
||||||
|
testParse("myaddd(x,y)=x+y; z={x: myaddd}; z", "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructRecord ('x': :myaddd))}; :z}")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
module Expression = Reducer_Expression
|
||||||
|
module ExpressionT = Reducer_Expression_T
|
||||||
|
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||||
|
module Parse = Reducer_Peggy_Parse
|
||||||
|
module ToExpression = Reducer_Peggy_ToExpression
|
||||||
|
module Result = Belt.Result
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
|
||||||
|
let rExpr = Parse.parse(expr)
|
||||||
|
->Result.map(ToExpression.fromNode)
|
||||||
|
let a1 = rExpr->ExpressionT.toStringResultOkless
|
||||||
|
if (v=="_") {
|
||||||
|
a1->expect->toBe(answer)
|
||||||
|
} else {
|
||||||
|
let a2 = rExpr->Result.flatMap(
|
||||||
|
expr => Expression.reduceExpression(expr, Belt.Map.String.empty, ExpressionValue.defaultEnvironment)
|
||||||
|
)->ExpressionValue.toStringResultOkless
|
||||||
|
(a1, a2)->expect->toEqual((answer, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) => test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
|
||||||
|
|
||||||
|
module MySkip = {
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) => Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
module MyOnly = {
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) => Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Peggy to Expression", () => {
|
||||||
|
describe("literals operators parenthesis", () => {
|
||||||
|
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
|
||||||
|
testToExpression("1", "(:$$block 1)", ~v="1", ())
|
||||||
|
testToExpression("'hello'", "(:$$block 'hello')", ~v="'hello'", ())
|
||||||
|
testToExpression("true", "(:$$block true)", ~v="true", ())
|
||||||
|
testToExpression("1+2", "(:$$block (:add 1 2))", ~v="3", ())
|
||||||
|
testToExpression("add(1,2)", "(:$$block (:add 1 2))", ~v="3", ())
|
||||||
|
testToExpression("(1)", "(:$$block 1)", ())
|
||||||
|
testToExpression("(1+2)", "(:$$block (:add 1 2))", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("unary", () => {
|
||||||
|
testToExpression("-1", "(:$$block (:unaryMinus 1))", ~v="-1", ())
|
||||||
|
testToExpression("!true", "(:$$block (:not true))", ~v="false", ())
|
||||||
|
testToExpression("1 + -1", "(:$$block (:add 1 (:unaryMinus 1)))", ~v="0", ())
|
||||||
|
testToExpression("-a[0]", "(:$$block (:unaryMinus (:$atIndex :a 0)))", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("multi-line", () => {
|
||||||
|
testToExpression("x=1; 2", "(:$$block (:$let :x (:$$block 1)) 2)", ~v="2", ())
|
||||||
|
testToExpression("x=1; y=2", "(:$$block (:$let :x (:$$block 1)) (:$let :y (:$$block 2)))", ~v="{x: 1,y: 2}", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("variables", () => {
|
||||||
|
testToExpression("x = 1", "(:$$block (:$let :x (:$$block 1)))", ~v="{x: 1}", ())
|
||||||
|
testToExpression("x", "(:$$block :x)", ~v=":x", ()) //TODO: value should return error
|
||||||
|
testToExpression("x = 1; x", "(:$$block (:$let :x (:$$block 1)) :x)", ~v="1", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("functions", () => {
|
||||||
|
testToExpression("identity(x) = x", "(:$$block (:$let :identity (:$$lambda [x] (:$$block :x))))", ~v="{identity: lambda(x=>internal code)}", ()) // Function definitions become lambda assignments
|
||||||
|
testToExpression("identity(x)", "(:$$block (:identity :x))", ()) // Note value returns error properly
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("arrays", () => {
|
||||||
|
testToExpression("[]", "(:$$block (:$constructArray ()))", ~v="[]", ())
|
||||||
|
testToExpression("[0, 1, 2]", "(:$$block (:$constructArray (0 1 2)))", ~v="[0,1,2]", ())
|
||||||
|
testToExpression("['hello', 'world']", "(:$$block (:$constructArray ('hello' 'world')))", ~v="['hello','world']", ())
|
||||||
|
testToExpression("([0,1,2])[1]", "(:$$block (:$atIndex (:$constructArray (0 1 2)) 1))", ~v="1", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("records", () => {
|
||||||
|
testToExpression("{a: 1, b: 2}", "(:$$block (:$constructRecord (('a' 1) ('b' 2))))", ~v="{a: 1,b: 2}", ())
|
||||||
|
testToExpression("{1+0: 1, 2+0: 2}", "(:$$block (:$constructRecord (((:add 1 0) 1) ((:add 2 0) 2))))", ()) // key can be any expression
|
||||||
|
testToExpression("record.property", "(:$$block (:$atIndex :record 'property'))", ())
|
||||||
|
testToExpression("record={property: 1}; record.property", "(:$$block (:$let :record (:$$block (:$constructRecord (('property' 1))))) (:$atIndex :record 'property'))", ~v="1", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("comments", () => {
|
||||||
|
testToExpression("1 # This is a line comment", "(:$$block 1)", ~v="1", ())
|
||||||
|
testToExpression("1 // This is a line comment", "(:$$block 1)", ~v="1", ())
|
||||||
|
testToExpression("1 /* This is a multi line comment */", "(:$$block 1)", ~v="1", ())
|
||||||
|
testToExpression("/* This is a multi line comment */ 1", "(:$$block 1)", ~v="1", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("ternary operator", () => {
|
||||||
|
testToExpression("true ? 1 : 0", "(:$$block (:$$ternary true 1 0))", ~v="1", ())
|
||||||
|
testToExpression("false ? 1 : 0", "(:$$block (:$$ternary false 1 0))", ~v="0", ())
|
||||||
|
testToExpression("true ? 1 : false ? 2 : 0", "(:$$block (:$$ternary true 1 (:$$ternary false 2 0)))", ~v="1", ()) // nested ternary
|
||||||
|
testToExpression("false ? 1 : false ? 2 : 0", "(:$$block (:$$ternary false 1 (:$$ternary false 2 0)))", ~v="0", ()) // nested ternary
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("if then else", () => {
|
||||||
|
testToExpression("if true then 2 else 3", "(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))", ())
|
||||||
|
testToExpression("if true then {2} else {3}", "(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))", ())
|
||||||
|
testToExpression(
|
||||||
|
"if false then {2} else if false then {4} else {5}",
|
||||||
|
"(:$$block (:$$ternary false (:$$block 2) (:$$ternary false (:$$block 4) (:$$block 5))))", ()
|
||||||
|
) //nested if
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("pipe", () => {
|
||||||
|
testToExpression("1 -> add(2)", "(:$$block (:add 1 2))", ~v="3", ())
|
||||||
|
testToExpression("-1 -> add(2)", "(:$$block (:add (:unaryMinus 1) 2))", ~v="1", ()) // note that unary has higher priority naturally
|
||||||
|
testToExpression("1 -> add(2) * 3", "(:$$block (:multiply (:add 1 2) 3))", ~v="9", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("elixir pipe", () => {
|
||||||
|
testToExpression("1 |> add(2)", "(:$$block (:add 1 2))", ~v="3", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
// see testParse for priorities of to and credibleIntervalToDistribution
|
||||||
|
|
||||||
|
describe("inner block", () => {
|
||||||
|
// inner blocks are 0 argument lambdas. They can be used whenever a value is required.
|
||||||
|
// Like lambdas they have a local scope.
|
||||||
|
testToExpression("y=99; x={y=1; y}", "(:$$block (:$let :y (:$$block 99)) (:$let :x (:$$block (:$let :y (:$$block 1)) :y)))", ~v="{x: 1,y: 99}", ())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("lambda", () => {
|
||||||
|
testToExpression("{|x| x}", "(:$$block (:$$lambda [x] (:$$block :x)))", ~v="lambda(x=>internal code)", ())
|
||||||
|
testToExpression("f={|x| x}", "(:$$block (:$let :f (:$$block (:$$lambda [x] (:$$block :x)))))", ~v="{f: lambda(x=>internal code)}", ())
|
||||||
|
testToExpression("f(x)=x","(:$$block (:$let :f (:$$lambda [x] (:$$block :x))))", ~v="{f: lambda(x=>internal code)}", ()) // Function definitions are lambda assignments
|
||||||
|
testToExpression("f(x)=x ? 1 : 0", "(:$$block (:$let :f (:$$lambda [x] (:$$block (:$$ternary :x 1 0)))))", ~v="{f: lambda(x=>internal code)}", ())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ let expectParseToBe = (expr: string, answer: string) =>
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
let expectEvalToBe = (expr: string, answer: string) =>
|
||||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
let expectEvalError = (expr: string) =>
|
||||||
|
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")
|
||||||
|
|
||||||
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
||||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
||||||
->ExpressionValue.toStringResult
|
->ExpressionValue.toStringResult
|
||||||
|
@ -29,6 +32,7 @@ let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, ans
|
||||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||||
test(desc, () => expectParseToBe(expr, answer))
|
test(desc, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
|
let testEvalError = expr => test(expr, () => expectEvalError(expr))
|
||||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||||
|
|
|
@ -2,62 +2,11 @@
|
||||||
open Jest
|
open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
// describe("Parse for Bindings", () => {
|
|
||||||
// testParseOuterToBe("x", "Ok((:$$bindExpression (:$$bindings) :x))")
|
|
||||||
// testParseOuterToBe("x+1", "Ok((:$$bindExpression (:$$bindings) (:add :x 1)))")
|
|
||||||
// testParseOuterToBe(
|
|
||||||
// "y = x+1; y",
|
|
||||||
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))",
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
|
|
||||||
// describe("Parse Partial", () => {
|
|
||||||
// testParsePartialToBe(
|
|
||||||
// "x",
|
|
||||||
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))",
|
|
||||||
// )
|
|
||||||
// testParsePartialToBe(
|
|
||||||
// "y=x",
|
|
||||||
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y :x)) (:$exportVariablesExpression)))",
|
|
||||||
// )
|
|
||||||
// testParsePartialToBe(
|
|
||||||
// "y=x+1",
|
|
||||||
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$exportVariablesExpression)))",
|
|
||||||
// )
|
|
||||||
// testParsePartialToBe(
|
|
||||||
// "y = x+1; z = y",
|
|
||||||
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$let :z :y)) (:$exportVariablesExpression)))",
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
|
|
||||||
describe("Eval with Bindings", () => {
|
describe("Eval with Bindings", () => {
|
||||||
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
||||||
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||||
testParseToBe("y = x+1; y", "Ok((:$$block (:$$block (:$let :y (:add :x 1)) :y)))")
|
testParseToBe("y = x+1; y", "Ok((:$$block (:$let :y (:$$block (:add :x 1))) :y))")
|
||||||
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||||
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
|
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
|
||||||
Partial code is a partial code fragment that is cut out from a larger code.
|
|
||||||
Therefore it does not end with an expression.
|
|
||||||
*/
|
|
||||||
// describe("Eval Partial", () => {
|
|
||||||
// testEvalPartialBindingsToBe(
|
|
||||||
// // A partial cannot end with an expression
|
|
||||||
// "x",
|
|
||||||
// list{("x", ExpressionValue.EvNumber(1.))},
|
|
||||||
// "Error(Assignment expected)",
|
|
||||||
// )
|
|
||||||
// testEvalPartialBindingsToBe("y=x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 1})")
|
|
||||||
// testEvalPartialBindingsToBe(
|
|
||||||
// "y=x+1",
|
|
||||||
// list{("x", ExpressionValue.EvNumber(1.))},
|
|
||||||
// "Ok({x: 1,y: 2})",
|
|
||||||
// )
|
|
||||||
// testEvalPartialBindingsToBe(
|
|
||||||
// "y = x+1; z = y",
|
|
||||||
// list{("x", ExpressionValue.EvNumber(1.))},
|
|
||||||
// "Ok({x: 1,y: 2,z: 2})",
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe("call and bindings", () => {
|
||||||
)
|
)
|
||||||
testParseToBe(
|
testParseToBe(
|
||||||
"f=99; g(x)=f; g(2)",
|
"f=99; g(x)=f; g(2)",
|
||||||
"Ok((:$$block (:$$block (:$let :f 99) (:$let :g (:$$lambda [x] (:$$block :f))) (:g 2))))",
|
"Ok((:$$block (:$let :f (:$$block 99)) (:$let :g (:$$lambda [x] (:$$block :f))) (:g 2)))",
|
||||||
)
|
)
|
||||||
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
||||||
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
||||||
|
@ -63,15 +63,26 @@ describe("call and bindings", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("function tricks", () => {
|
describe("function tricks", () => {
|
||||||
testParseToBe(
|
testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
|
||||||
"f(x)=f(y)=2; f(2)",
|
|
||||||
"Ok((:$$block (:$$block (:$let :f (:$$lambda [x] (:$$block (:$let :f (:$$lambda [y] (:$$block 2)))))) (:f 2))))",
|
|
||||||
)
|
|
||||||
testEvalToBe("f(x)=f(y)=2; f(2)", "Ok({f: lambda(y=>internal code),x: 2})")
|
|
||||||
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
|
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
|
||||||
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})")
|
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})")
|
||||||
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
|
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
|
||||||
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
|
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
|
||||||
MySkip.testEvalToBe("myadd(x,y)=x+y; z=[add]; z[0](3,2)", "????") //TODO: to fix with new parser
|
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
|
||||||
MySkip.testEvalToBe("myaddd(x,y)=x+y; z={x: add}; z.x(3,2)", "????") //TODO: to fix with new parser
|
testEvalToBe("myadd(x,y)=x+y; z=myadd; z(1, 1)", "Ok(2)")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("lambda in structures", () => {
|
||||||
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok({myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})")
|
||||||
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
|
||||||
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
|
||||||
|
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
|
||||||
|
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x", "Ok(lambda(x,y=>internal code))")
|
||||||
|
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x(3,2)", "Ok(5)")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("ternary and bindings", () => {
|
||||||
|
testEvalToBe("f(x)=x ? 1 : 0; f(true)", "Ok(1)")
|
||||||
|
testEvalToBe("f(x)=x>2 ? 1 : 0; f(3)", "Ok(1)")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,9 @@
|
||||||
open Jest
|
open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
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((:$$block 1))")
|
|
||||||
testParseToBe("(1)", "Ok((:$$block 1))")
|
|
||||||
testParseToBe("1+2", "Ok((:$$block (:add 1 2)))")
|
|
||||||
testParseToBe("1+2*3", "Ok((:$$block (: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)
|
|
||||||
testDescriptionParseToBe("empty", "[]", "Ok((:$$block ()))")
|
|
||||||
testParseToBe("[1, 2, 3]", "Ok((:$$block (1 2 3)))")
|
|
||||||
testParseToBe("['hello', 'world']", "Ok((:$$block ('hello' 'world')))")
|
|
||||||
testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$$block (:$atIndex (0 1 2) (1))))")
|
|
||||||
})
|
|
||||||
describe("records", () => {
|
|
||||||
testDescriptionParseToBe(
|
|
||||||
"define",
|
|
||||||
"{a: 1, b: 2}",
|
|
||||||
"Ok((:$$block (:$constructRecord (('a' 1) ('b' 2)))))",
|
|
||||||
)
|
|
||||||
testDescriptionParseToBe(
|
|
||||||
"use",
|
|
||||||
"{a: 1, b: 2}.a",
|
|
||||||
"Ok((:$$block (:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a'))))",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
describe("multi-line", () => {
|
|
||||||
testParseToBe("1; 2", "Ok((:$$block (:$$block 1 2)))")
|
|
||||||
testParseToBe("1+1; 2+1", "Ok((:$$block (:$$block (:add 1 1) (:add 2 1))))")
|
|
||||||
})
|
|
||||||
describe("assignment", () => {
|
|
||||||
testParseToBe("x=1; x", "Ok((:$$block (:$$block (:$let :x 1) :x)))")
|
|
||||||
testParseToBe("x=1+1; x+1", "Ok((:$$block (:$$block (:$let :x (:add 1 1)) (:add :x 1))))")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("eval", () => {
|
describe("eval", () => {
|
||||||
// All MathJs operators and functions are builtin for string, float and boolean
|
// All MathJs operators and functions are builtin for string, float and boolean
|
||||||
// .e.g + - / * > >= < <= == /= not and or
|
// .e.g + - / * > >= < <= == /= not and or
|
||||||
// See https://mathjs.org/docs/expressions/syntax.html
|
|
||||||
// See https://mathjs.org/docs/reference/functions.html
|
// See https://mathjs.org/docs/reference/functions.html
|
||||||
describe("expressions", () => {
|
describe("expressions", () => {
|
||||||
testEvalToBe("1", "Ok(1)")
|
testEvalToBe("1", "Ok(1)")
|
||||||
|
@ -70,20 +24,21 @@ describe("eval", () => {
|
||||||
})
|
})
|
||||||
describe("records", () => {
|
describe("records", () => {
|
||||||
test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1,b: 2})"))
|
test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1,b: 2})"))
|
||||||
test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)"))
|
test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
|
||||||
test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)"))
|
test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
|
||||||
|
testEvalError("{a: 1}.b") // invalid syntax
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("multi-line", () => {
|
describe("multi-line", () => {
|
||||||
testEvalToBe("1; 2", "Error(Assignment expected)")
|
testEvalError("1; 2")
|
||||||
testEvalToBe("1+1; 2+1", "Error(Assignment expected)")
|
testEvalError("1+1; 2+1")
|
||||||
})
|
})
|
||||||
describe("assignment", () => {
|
describe("assignment", () => {
|
||||||
testEvalToBe("x=1; x", "Ok(1)")
|
testEvalToBe("x=1; x", "Ok(1)")
|
||||||
testEvalToBe("x=1+1; x+1", "Ok(3)")
|
testEvalToBe("x=1+1; x+1", "Ok(3)")
|
||||||
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
|
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
|
||||||
testEvalToBe("1; x=1", "Error(Assignment expected)")
|
testEvalError("1; x=1")
|
||||||
testEvalToBe("1; 1", "Error(Assignment expected)")
|
testEvalError("1; 1")
|
||||||
testEvalToBe("x=1; x=1", "Ok({x: 1})")
|
testEvalToBe("x=1; x=1", "Ok({x: 1})")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -94,9 +49,9 @@ describe("test exceptions", () => {
|
||||||
"javascriptraise('div by 0')",
|
"javascriptraise('div by 0')",
|
||||||
"Error(JS Exception: Error: 'div by 0')",
|
"Error(JS Exception: Error: 'div by 0')",
|
||||||
)
|
)
|
||||||
testDescriptionEvalToBe(
|
// testDescriptionEvalToBe(
|
||||||
"rescript exception",
|
// "rescript exception",
|
||||||
"rescriptraise()",
|
// "rescriptraise()",
|
||||||
"Error(TODO: unhandled rescript exception)",
|
// "Error(TODO: unhandled rescript exception)",
|
||||||
)
|
// )
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"homepage": "https://squiggle-language.com",
|
"homepage": "https://squiggle-language.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"peggy": "peggy --cache ",
|
||||||
"build": "yarn build:rescript && yarn build:typescript",
|
"build": "yarn build:rescript && yarn build:typescript",
|
||||||
"build:rescript": "rescript build -with-deps",
|
"build:rescript": "rescript build -with-deps",
|
||||||
"build:typescript": "tsc",
|
"build:typescript": "tsc",
|
||||||
|
|
|
@ -13,6 +13,8 @@ open Reducer_ErrorValue
|
||||||
DO NOT try to add external function mapping here!
|
DO NOT try to add external function mapping here!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//TODO: pow to xor
|
||||||
|
|
||||||
exception TestRescriptException
|
exception TestRescriptException
|
||||||
|
|
||||||
let callInternal = (call: functionCall, environment, reducer: ExpressionT.reducerFn): result<
|
let callInternal = (call: functionCall, environment, reducer: ExpressionT.reducerFn): result<
|
||||||
|
@ -116,9 +118,9 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
||||||
}
|
}
|
||||||
|
|
||||||
switch call {
|
switch call {
|
||||||
| ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) =>
|
| ("$atIndex", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
|
||||||
arrayAtIndex(aValueArray, fIndex)
|
| ("$atIndex", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
|
||||||
| ("$atIndex", [EvRecord(dict), EvArray([EvString(sIndex)])]) => recordAtIndex(dict, sIndex)
|
| ("$constructArray", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
|
||||||
| ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
| ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
||||||
| ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
| ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
||||||
| ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
| ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
Macros are used to define language building blocks. They are like Lisp macros.
|
Macros are used to define language building blocks. They are like Lisp macros.
|
||||||
*/
|
*/
|
||||||
module Bindings = Reducer_Expression_Bindings
|
module Bindings = Reducer_Expression_Bindings
|
||||||
|
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
module ExpressionWithContext = Reducer_ExpressionWithContext
|
module ExpressionWithContext = Reducer_ExpressionWithContext
|
||||||
|
@ -139,7 +140,8 @@ let dispatchMacroCall = (
|
||||||
bindings: ExpressionT.bindings,
|
bindings: ExpressionT.bindings,
|
||||||
environment,
|
environment,
|
||||||
): result<expressionWithContext, errorValue> => {
|
): result<expressionWithContext, errorValue> => {
|
||||||
let rCondition = reduceExpression(condition, bindings, environment)
|
let blockCondition = ExpressionBuilder.eBlock(list{condition})
|
||||||
|
let rCondition = reduceExpression(blockCondition, bindings, environment)
|
||||||
rCondition->Result.flatMap(conditionValue =>
|
rCondition->Result.flatMap(conditionValue =>
|
||||||
switch conditionValue {
|
switch conditionValue {
|
||||||
| ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok
|
| ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok
|
||||||
|
|
|
@ -18,13 +18,10 @@ type internalCode = ReducerInterface_ExpressionValue.internalCode
|
||||||
type t = expression
|
type t = expression
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Converts a MathJs code to expression
|
Converts a Squigle code to expression
|
||||||
*/
|
*/
|
||||||
let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
let parse = (peggyCode: string): result<t, errorValue> =>
|
||||||
expr->parser->Result.flatMap(node => converter(node))
|
peggyCode->Reducer_Peggy_Parse.parse->Result.map(node => Reducer_Peggy_ToExpression.fromNode(node))
|
||||||
|
|
||||||
let parse = (mathJsCode: string): result<t, errorValue> =>
|
|
||||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Recursively evaluate/reduce the expression (Lisp AST)
|
Recursively evaluate/reduce the expression (Lisp AST)
|
||||||
|
@ -75,11 +72,17 @@ and reduceExpressionList = (
|
||||||
and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
||||||
expressionValue,
|
expressionValue,
|
||||||
'e,
|
'e,
|
||||||
> =>
|
> =>
|
||||||
switch valueList {
|
switch valueList {
|
||||||
| list{EvCall(fName), ...args} =>
|
| list{EvCall(fName), ...args} =>
|
||||||
(fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment, reduceExpression)
|
(fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment, reduceExpression)
|
||||||
|
|
||||||
|
| list{EvLambda(_lamdaCall)} =>
|
||||||
|
valueList
|
||||||
|
->Lambda.checkIfReduced
|
||||||
|
->Result.flatMap(reducedValueList =>
|
||||||
|
reducedValueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||||
|
)
|
||||||
| list{EvLambda(lamdaCall), ...args} =>
|
| list{EvLambda(lamdaCall), ...args} =>
|
||||||
Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression)
|
Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression)
|
||||||
| _ =>
|
| _ =>
|
||||||
|
|
|
@ -42,6 +42,12 @@ let toStringResult = codeResult =>
|
||||||
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toStringResultOkless = codeResult =>
|
||||||
|
switch codeResult {
|
||||||
|
| Ok(a) => toString(a)
|
||||||
|
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
||||||
|
}
|
||||||
|
|
||||||
let inspect = (expr: expression): expression => {
|
let inspect = (expr: expression): expression => {
|
||||||
Js.log(toString(expr))
|
Js.log(toString(expr))
|
||||||
expr
|
expr
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
module Eval = Reducer_MathJs_Eval
|
module Eval = Reducer_MathJs_Eval
|
||||||
module Parse = Reducer_MathJs_Parse
|
|
||||||
module ToExpression = Reducer_MathJs_ToExpression
|
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
/*
|
|
||||||
MathJs Nodes
|
|
||||||
We make MathJs Nodes strong-typed
|
|
||||||
*/
|
|
||||||
module Extra = Reducer_Extra
|
|
||||||
open Reducer_ErrorValue
|
|
||||||
|
|
||||||
type node = {"type": string, "isNode": bool, "comment": string}
|
|
||||||
type arrayNode = {...node, "items": array<node>}
|
|
||||||
type block = {"node": node}
|
|
||||||
type blockNode = {...node, "blocks": array<block>}
|
|
||||||
type conditionalNode = {...node, "condition": node, "trueExpr": node, "falseExpr": node}
|
|
||||||
type constantNode = {...node, "value": unit}
|
|
||||||
type functionAssignmentNode = {...node, "name": string, "params": array<string>, "expr": node}
|
|
||||||
type indexNode = {...node, "dimensions": array<node>}
|
|
||||||
type objectNode = {...node, "properties": Js.Dict.t<node>}
|
|
||||||
type accessorNode = {...node, "object": node, "index": indexNode, "name": string}
|
|
||||||
type parenthesisNode = {...node, "content": node}
|
|
||||||
//rangeNode
|
|
||||||
//relationalNode
|
|
||||||
type symbolNode = {...node, "name": string}
|
|
||||||
type functionNode = {...node, "fn": unit, "args": array<node>}
|
|
||||||
type operatorNode = {...functionNode, "op": string}
|
|
||||||
type assignmentNode = {...node, "object": symbolNode, "value": node}
|
|
||||||
type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node}
|
|
||||||
type assignmentNodeWIndex = {...assignmentNodeWAccessor, "index": Js.null<indexNode>}
|
|
||||||
|
|
||||||
external castAccessorNode: node => accessorNode = "%identity"
|
|
||||||
external castArrayNode: node => arrayNode = "%identity"
|
|
||||||
external castAssignmentNode: node => assignmentNode = "%identity"
|
|
||||||
external castAssignmentNodeWAccessor: node => assignmentNodeWAccessor = "%identity"
|
|
||||||
external castAssignmentNodeWIndex: node => assignmentNodeWIndex = "%identity"
|
|
||||||
external castBlockNode: node => blockNode = "%identity"
|
|
||||||
external castConditionalNode: node => conditionalNode = "%identity"
|
|
||||||
external castConstantNode: node => constantNode = "%identity"
|
|
||||||
external castFunctionAssignmentNode: node => functionAssignmentNode = "%identity"
|
|
||||||
external castFunctionNode: node => functionNode = "%identity"
|
|
||||||
external castIndexNode: node => indexNode = "%identity"
|
|
||||||
external castObjectNode: node => objectNode = "%identity"
|
|
||||||
external castOperatorNode: node => operatorNode = "%identity"
|
|
||||||
external castOperatorNodeToFunctionNode: operatorNode => functionNode = "%identity"
|
|
||||||
external castParenthesisNode: node => parenthesisNode = "%identity"
|
|
||||||
external castSymbolNode: node => symbolNode = "%identity"
|
|
||||||
|
|
||||||
/*
|
|
||||||
MathJs Parser
|
|
||||||
*/
|
|
||||||
@module("mathjs") external parse__: string => node = "parse"
|
|
||||||
|
|
||||||
let parse = (expr: string): result<node, errorValue> =>
|
|
||||||
try {
|
|
||||||
Ok(parse__(expr))
|
|
||||||
} catch {
|
|
||||||
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type mathJsNode =
|
|
||||||
| MjAccessorNode(accessorNode)
|
|
||||||
| MjArrayNode(arrayNode)
|
|
||||||
| MjAssignmentNode(assignmentNode)
|
|
||||||
| MjBlockNode(blockNode)
|
|
||||||
| MjConditionalNode(conditionalNode)
|
|
||||||
| MjConstantNode(constantNode)
|
|
||||||
| MjFunctionAssignmentNode(functionAssignmentNode)
|
|
||||||
| MjFunctionNode(functionNode)
|
|
||||||
| MjIndexNode(indexNode)
|
|
||||||
| MjObjectNode(objectNode)
|
|
||||||
| MjOperatorNode(operatorNode)
|
|
||||||
| MjParenthesisNode(parenthesisNode)
|
|
||||||
| MjSymbolNode(symbolNode)
|
|
||||||
|
|
||||||
let castNodeType = (node: node) => {
|
|
||||||
let decideAssignmentNode = node => {
|
|
||||||
let iNode = node->castAssignmentNodeWIndex
|
|
||||||
if Js.null == iNode["index"] && iNode["object"]["type"] == "SymbolNode" {
|
|
||||||
node->castAssignmentNode->MjAssignmentNode->Ok
|
|
||||||
} else {
|
|
||||||
RESyntaxError("Assignment to index or property not supported")->Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node["type"] {
|
|
||||||
| "AccessorNode" => node->castAccessorNode->MjAccessorNode->Ok
|
|
||||||
| "ArrayNode" => node->castArrayNode->MjArrayNode->Ok
|
|
||||||
| "AssignmentNode" => node->decideAssignmentNode
|
|
||||||
| "BlockNode" => node->castBlockNode->MjBlockNode->Ok
|
|
||||||
| "ConditionalNode" => node->castConditionalNode->MjConditionalNode->Ok
|
|
||||||
| "ConstantNode" => node->castConstantNode->MjConstantNode->Ok
|
|
||||||
| "FunctionAssignmentNode" => node->castFunctionAssignmentNode->MjFunctionAssignmentNode->Ok
|
|
||||||
| "FunctionNode" => node->castFunctionNode->MjFunctionNode->Ok
|
|
||||||
| "IndexNode" => node->castIndexNode->MjIndexNode->Ok
|
|
||||||
| "ObjectNode" => node->castObjectNode->MjObjectNode->Ok
|
|
||||||
| "OperatorNode" => node->castOperatorNode->MjOperatorNode->Ok
|
|
||||||
| "ParenthesisNode" => node->castParenthesisNode->MjParenthesisNode->Ok
|
|
||||||
| "SymbolNode" => node->castSymbolNode->MjSymbolNode->Ok
|
|
||||||
| _ => RETodo(`Argg, unhandled MathJsNode: ${node["type"]}`)->Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
external unitAsSymbolNode: unit => symbolNode = "%identity"
|
|
||||||
external unitAsString: unit => string = "%identity"
|
|
||||||
|
|
||||||
let nameOfFunctionNode = (fNode: functionNode): string => {
|
|
||||||
let name = fNode["fn"]
|
|
||||||
if Js.typeof(name) == "string" {
|
|
||||||
name->unitAsString
|
|
||||||
} else {
|
|
||||||
(name->unitAsSymbolNode)["name"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let rec toString = (mathJsNode: mathJsNode): string => {
|
|
||||||
let toStringValue = (a: 'a): string =>
|
|
||||||
if Js.typeof(a) == "string" {
|
|
||||||
`'${Js.String.make(a)}'`
|
|
||||||
} else {
|
|
||||||
Js.String.make(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
let toStringNodeArray = (nodeArray: array<node>): string =>
|
|
||||||
nodeArray
|
|
||||||
->Belt.Array.map(a => toStringMathJsNode(a))
|
|
||||||
->Extra.Array.interperse(", ")
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
let toStringFunctionAssignmentNode = (faNode: functionAssignmentNode): string => {
|
|
||||||
let paramNames = Js.Array2.toString(faNode["params"])
|
|
||||||
`${faNode["name"]} = (${paramNames}) => ${toStringMathJsNode(faNode["expr"])}`
|
|
||||||
}
|
|
||||||
let toStringFunctionNode = (fnode: functionNode): string =>
|
|
||||||
`${fnode->nameOfFunctionNode}(${fnode["args"]->toStringNodeArray})`
|
|
||||||
|
|
||||||
let toStringObjectEntry = ((key: string, value: node)): string =>
|
|
||||||
`${key}: ${value->toStringMathJsNode}`
|
|
||||||
|
|
||||||
let toStringObjectNode = (oNode: objectNode): string =>
|
|
||||||
`{${oNode["properties"]
|
|
||||||
->Js.Dict.entries
|
|
||||||
->Belt.Array.map(entry => entry->toStringObjectEntry)
|
|
||||||
->Extra.Array.interperse(", ")
|
|
||||||
->Js.String.concatMany("")}}`
|
|
||||||
|
|
||||||
let toStringIndexNode = (iNode: indexNode): string =>
|
|
||||||
iNode["dimensions"]
|
|
||||||
->Belt.Array.map(each => toStringResult(each->castNodeType))
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
let toStringSymbolNode = (sNode: symbolNode): string => sNode["name"]
|
|
||||||
|
|
||||||
let toStringBlocks = (blocks: array<block>): string =>
|
|
||||||
blocks
|
|
||||||
->Belt.Array.map(each => each["node"]->castNodeType->toStringResult)
|
|
||||||
->Extra.Array.interperse("; ")
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
switch mathJsNode {
|
|
||||||
| MjAccessorNode(aNode) =>
|
|
||||||
`${aNode["object"]->toStringMathJsNode}[${aNode["index"]->toStringIndexNode}]`
|
|
||||||
| MjArrayNode(aNode) => `[${aNode["items"]->toStringNodeArray}]`
|
|
||||||
| MjAssignmentNode(aNode) =>
|
|
||||||
`${aNode["object"]->toStringSymbolNode} = ${aNode["value"]->toStringMathJsNode}`
|
|
||||||
| MjBlockNode(bNode) => `{${bNode["blocks"]->toStringBlocks}}`
|
|
||||||
| MjConditionalNode(cNode) =>
|
|
||||||
`ternary(${toStringMathJsNode(cNode["condition"])}, ${toStringMathJsNode(
|
|
||||||
cNode["trueExpr"],
|
|
||||||
)}, ${toStringMathJsNode(cNode["falseExpr"])})`
|
|
||||||
| MjConstantNode(cNode) => cNode["value"]->toStringValue
|
|
||||||
| MjFunctionAssignmentNode(faNode) => faNode->toStringFunctionAssignmentNode
|
|
||||||
| MjFunctionNode(fNode) => fNode->toStringFunctionNode
|
|
||||||
| MjIndexNode(iNode) => iNode->toStringIndexNode
|
|
||||||
| MjObjectNode(oNode) => oNode->toStringObjectNode
|
|
||||||
| MjOperatorNode(opNode) => opNode->castOperatorNodeToFunctionNode->toStringFunctionNode
|
|
||||||
| MjParenthesisNode(pNode) => `(${toStringMathJsNode(pNode["content"])})`
|
|
||||||
| MjSymbolNode(sNode) => sNode->toStringSymbolNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
and toStringResult = (rMathJsNode: result<mathJsNode, errorValue>): string =>
|
|
||||||
switch rMathJsNode {
|
|
||||||
| Error(e) => errorToString(e)
|
|
||||||
| Ok(mathJsNode) => toString(mathJsNode)
|
|
||||||
}
|
|
||||||
and toStringMathJsNode = node => node->castNodeType->toStringResult
|
|
|
@ -1,154 +0,0 @@
|
||||||
/* * WARNING. DO NOT EDIT, BEAUTIFY, COMMENT ON OR REFACTOR THIS CODE.
|
|
||||||
We will stop using MathJs parser and
|
|
||||||
this whole file will go to trash
|
|
||||||
**/
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
|
||||||
module JavaScript = Reducer_Js
|
|
||||||
module Parse = Reducer_MathJs_Parse
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
type errorValue = ErrorValue.errorValue
|
|
||||||
type expression = ExpressionT.expression
|
|
||||||
type expressionValue = ExpressionValue.expressionValue
|
|
||||||
|
|
||||||
let blockToNode = block => block["node"]
|
|
||||||
|
|
||||||
let rec fromInnerNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|
||||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
|
||||||
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
|
||||||
Belt.List.reduceReverse(nodeList, Ok(list{}), (racc, currNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let caseFunctionNode = fNode => {
|
|
||||||
let rLispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
|
||||||
rLispArgs->Result.map(lispArgs =>
|
|
||||||
ExpressionBuilder.eFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseObjectNode = oNode => {
|
|
||||||
let fromObjectEntries = entryList => {
|
|
||||||
let rargs = Belt.List.reduceReverse(entryList, Ok(list{}), (
|
|
||||||
racc,
|
|
||||||
(key: string, value: Parse.node),
|
|
||||||
) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(value)->Result.map(valueExpression => {
|
|
||||||
let entryCode =
|
|
||||||
list{ExpressionBuilder.eString(key), valueExpression}->ExpressionT.EList
|
|
||||||
list{entryCode, ...acc}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rargs->Result.flatMap(args =>
|
|
||||||
ExpressionBuilder.eFunction("$constructRecord", list{ExpressionT.EList(args)})->Ok
|
|
||||||
) // $constructRecord gets a single argument: List of key-value paiers
|
|
||||||
}
|
|
||||||
|
|
||||||
oNode["properties"]->Js.Dict.entries->Belt.List.fromArray->fromObjectEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseIndexNode = iNode => {
|
|
||||||
let rpropertyCodeList = Belt.List.reduceReverse(
|
|
||||||
iNode["dimensions"]->Belt.List.fromArray,
|
|
||||||
Ok(list{}),
|
|
||||||
(racc, currentPropertyMathJsNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{
|
|
||||||
propertyCode,
|
|
||||||
...acc,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
rpropertyCodeList->Result.map(propertyCodeList => ExpressionT.EList(propertyCodeList))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseAccessorNode = (objectNode, indexNode) => {
|
|
||||||
caseIndexNode(indexNode)->Result.flatMap(indexCode => {
|
|
||||||
fromInnerNode(objectNode)->Result.flatMap(objectCode =>
|
|
||||||
ExpressionBuilder.eFunction("$atIndex", list{objectCode, indexCode})->Ok
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseBlock = (nodesArray: array<Parse.node>): result<expression, errorValue> => {
|
|
||||||
let rStatements: result<list<expression>, 'a> =
|
|
||||||
nodesArray
|
|
||||||
->Belt.List.fromArray
|
|
||||||
->Belt.List.reduceReverse(Ok(list{}), (racc, currNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rStatements->Result.map(statements => ExpressionBuilder.eBlock(statements))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseAssignmentNode = aNode => {
|
|
||||||
let symbolName = aNode["object"]["name"]
|
|
||||||
let rValueExpression = fromInnerNode(aNode["value"])
|
|
||||||
rValueExpression->Result.map(valueExpression =>
|
|
||||||
ExpressionBuilder.eLetStatement(symbolName, valueExpression)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseFunctionAssignmentNode = faNode => {
|
|
||||||
let symbol = faNode["name"]->ExpressionBuilder.eSymbol
|
|
||||||
let rValueExpression = fromInnerNode(faNode["expr"])
|
|
||||||
|
|
||||||
rValueExpression->Result.flatMap(valueExpression => {
|
|
||||||
let lispParams = ExpressionBuilder.eArrayString(faNode["params"])
|
|
||||||
let valueBlock = ExpressionBuilder.eBlock(list{valueExpression})
|
|
||||||
let lambda = ExpressionBuilder.eFunction("$$lambda", list{lispParams, valueBlock})
|
|
||||||
ExpressionBuilder.eFunction("$let", list{symbol, lambda})->Ok
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseArrayNode = aNode => {
|
|
||||||
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseConditionalNode = cndNode => {
|
|
||||||
let rCondition = fromInnerNode(cndNode["condition"])
|
|
||||||
let rTrueExpr = fromInnerNode(cndNode["trueExpr"])
|
|
||||||
let rFalse = fromInnerNode(cndNode["falseExpr"])
|
|
||||||
|
|
||||||
rCondition->Result.flatMap(condition =>
|
|
||||||
rTrueExpr->Result.flatMap(trueExpr =>
|
|
||||||
rFalse->Result.flatMap(falseExpr =>
|
|
||||||
ExpressionBuilder.eFunction("$$ternary", list{condition, trueExpr, falseExpr})->Ok
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
|
||||||
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
|
||||||
| MjArrayNode(aNode) => caseArrayNode(aNode)
|
|
||||||
| MjAssignmentNode(aNode) => caseAssignmentNode(aNode)
|
|
||||||
| MjSymbolNode(sNode) => {
|
|
||||||
let expr: expression = ExpressionBuilder.eSymbol(sNode["name"])
|
|
||||||
let rExpr: result<expression, errorValue> = expr->Ok
|
|
||||||
rExpr
|
|
||||||
}
|
|
||||||
| MjBlockNode(bNode) => bNode["blocks"]->Js.Array2.map(blockToNode)->caseBlock
|
|
||||||
| MjConditionalNode(cndNode) => caseConditionalNode(cndNode)
|
|
||||||
| MjConstantNode(cNode) =>
|
|
||||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
|
||||||
| MjFunctionAssignmentNode(faNode) => caseFunctionAssignmentNode(faNode)
|
|
||||||
| MjFunctionNode(fNode) => fNode->caseFunctionNode
|
|
||||||
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
|
||||||
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
|
||||||
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->caseFunctionNode
|
|
||||||
| MjParenthesisNode(pNode) => pNode["content"]->fromInnerNode
|
|
||||||
}
|
|
||||||
rFinalExpression
|
|
||||||
})
|
|
||||||
|
|
||||||
let fromNode = (node: Parse.node): result<expression, errorValue> =>
|
|
||||||
fromInnerNode(node)->Result.map(expr => ExpressionBuilder.eBlock(list{expr}))
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,321 @@
|
||||||
|
// Try in https://peggyjs.org/online
|
||||||
|
|
||||||
|
{{
|
||||||
|
var toFunction = {
|
||||||
|
'-': 'subtract',
|
||||||
|
'->': 'pipe',
|
||||||
|
'!=': 'unequal',
|
||||||
|
'.-': 'dotSubtract',
|
||||||
|
'.*': 'dotMultiply',
|
||||||
|
'./': 'dotDivide',
|
||||||
|
'.^': 'dotPow',
|
||||||
|
'.+': 'dotAdd',
|
||||||
|
'*': 'multiply',
|
||||||
|
'/': 'divide',
|
||||||
|
'&&': 'and',
|
||||||
|
'^': 'pow', // or xor
|
||||||
|
'+': 'add',
|
||||||
|
'<': 'smaller',
|
||||||
|
'<=': 'smallerEq',
|
||||||
|
'==': 'equal',
|
||||||
|
'>': 'larger',
|
||||||
|
'>=': 'largerEq',
|
||||||
|
'||': 'or',
|
||||||
|
'to': 'credibleIntervalToDistribution',
|
||||||
|
}
|
||||||
|
|
||||||
|
var unaryToFunction = {
|
||||||
|
'-': 'unaryMinus',
|
||||||
|
'!': 'not',
|
||||||
|
'.-': 'unaryDotMinus',
|
||||||
|
}
|
||||||
|
|
||||||
|
var postOperatorToFunction = {
|
||||||
|
'.': '$atIndex',
|
||||||
|
'()': '$$applyAll',
|
||||||
|
'[]': '$atIndex',
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeBlock(statements) {return{type: 'Block', statements: statements}}
|
||||||
|
function nodeBoolean(value) {return {type: 'Boolean', value: value}}
|
||||||
|
function nodeCallIndentifier(value) {return {type: 'CallIdentifier', value: value}}
|
||||||
|
function nodeExpression(args) {return {type: 'Expression', nodes: args}}
|
||||||
|
function nodeFloat(value) {return {type: 'Float', value: value}}
|
||||||
|
function makeFunctionCall(fn, args) {
|
||||||
|
if (fn === '$$applyAll') {
|
||||||
|
// Any list of values is applied from left to right anyway.
|
||||||
|
// Like in Haskell and Lisp.
|
||||||
|
// So we remove the redundant $$applyAll.
|
||||||
|
if (args[0].type === "Identifier") {args[0].type = "CallIdentifier"}
|
||||||
|
return nodeExpression(args)
|
||||||
|
} else {
|
||||||
|
return nodeExpression([nodeCallIndentifier(fn), ...args])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function nodeIdentifier(value) {return {type: 'Identifier', value: value}}
|
||||||
|
function nodeInteger(value) {return {type: 'Integer', value: value}}
|
||||||
|
function nodeKeyValue(key, value) {
|
||||||
|
if (key.type === 'Identifier') {key.type = 'String'}
|
||||||
|
return {type: 'KeyValue', key: key, value: value}}
|
||||||
|
function nodeLambda(args, body) {return {type: 'Lambda', args: args, body: body}}
|
||||||
|
function nodeLetStatment(variable, value) {return {type: 'LetStatement', variable: variable, value: value}}
|
||||||
|
function nodeString(value) {return {type: 'String', value: value}}
|
||||||
|
function nodeTernary(condition, trueExpression, falseExpression) {return {type: 'Ternary', condition: condition, trueExpression: trueExpression, falseExpression: falseExpression}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
start
|
||||||
|
= _nl start:outerBlock _nl finalComment? {return start}
|
||||||
|
|
||||||
|
zeroOMoreArgumentsBlock = innerBlock / lambda
|
||||||
|
|
||||||
|
outerBlock
|
||||||
|
= statements:array_statements finalExpression: (statementSeparator @expression)?
|
||||||
|
{ if (finalExpression != null) { statements.push(finalExpression) }
|
||||||
|
return nodeBlock(statements) }
|
||||||
|
/ finalExpression: expression
|
||||||
|
{ return nodeBlock([finalExpression])}
|
||||||
|
|
||||||
|
innerBlock
|
||||||
|
= '{' _ statements:array_statements finalExpression: (statementSeparator @expression) _ '}'
|
||||||
|
{ statements.push(finalExpression)
|
||||||
|
return nodeBlock(statements) }
|
||||||
|
/ '{' _ finalExpression: expression _ '}'
|
||||||
|
{ return nodeBlock([finalExpression]) }
|
||||||
|
/ finalExpression: expression
|
||||||
|
{ return nodeBlock([finalExpression])}
|
||||||
|
|
||||||
|
array_statements
|
||||||
|
= head:statement tail:(statementSeparator @array_statements )
|
||||||
|
{ return [head, ...tail] }
|
||||||
|
/ head:statement
|
||||||
|
{ return [head] }
|
||||||
|
|
||||||
|
statement
|
||||||
|
= letStatement
|
||||||
|
/ defunStatement
|
||||||
|
|
||||||
|
letStatement
|
||||||
|
= variable:identifier _ '=' _nl value:zeroOMoreArgumentsBlock
|
||||||
|
|
||||||
|
{ return nodeLetStatment(variable, value) }
|
||||||
|
|
||||||
|
defunStatement
|
||||||
|
= variable:identifier '(' _nl args:array_parameters _nl ')' _ '=' _nl body:innerBlock
|
||||||
|
{ var value = nodeLambda(args, body)
|
||||||
|
return nodeLetStatment(variable, value) }
|
||||||
|
|
||||||
|
array_parameters
|
||||||
|
= head:identifier tail:(_ ',' _nl @identifier)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
expression = ifthenelse / ternary / logicalAdditive
|
||||||
|
|
||||||
|
ifthenelse
|
||||||
|
= 'if' __nl condition:logicalAdditive
|
||||||
|
__nl 'then' __nl trueExpression:innerBlock
|
||||||
|
__nl 'else' __nl falseExpression:(ifthenelse/innerBlock)
|
||||||
|
{ return nodeTernary(condition, trueExpression, falseExpression) }
|
||||||
|
|
||||||
|
ternary
|
||||||
|
= condition:logicalAdditive _ '?' _nl trueExpression:logicalAdditive _ ':' _nl falseExpression:(ternary/logicalAdditive)
|
||||||
|
{ return nodeTernary(condition, trueExpression, falseExpression) }
|
||||||
|
|
||||||
|
logicalAdditive
|
||||||
|
= head:logicalMultiplicative tail:(_ operator:'&&' _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
// start binary operators
|
||||||
|
logicalMultiplicative
|
||||||
|
= head:equality tail:(_ operator:'||' _nl arg:equality {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
equality
|
||||||
|
= left:relational _ operator:('=='/'!=') _nl right:relational
|
||||||
|
{ return makeFunctionCall(toFunction[operator], [left, right])}
|
||||||
|
/ relational
|
||||||
|
|
||||||
|
relational
|
||||||
|
= left:additive _ operator:('<='/'<'/'>='/'>') _nl right:additive
|
||||||
|
{ return makeFunctionCall(toFunction[operator], [left, right])}
|
||||||
|
/ additive
|
||||||
|
|
||||||
|
additive
|
||||||
|
= head:multiplicative tail:(_ operator:('+' / '-' / '.+' / '.-') _nl arg:multiplicative {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
multiplicative
|
||||||
|
= head:power tail:(_ operator:('*' / '/' / '.*' / './') _nl arg:power {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
power
|
||||||
|
= head:credibleInterval tail:(_ operator:('^' / '.^') _nl arg:credibleInterval {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
credibleInterval
|
||||||
|
= head:chainFunctionCall tail:(__ operator:('to') __nl arg:chainFunctionCall {return {operator: operator, right: arg}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(toFunction[element.operator], [result, element.right])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
chainFunctionCall
|
||||||
|
= head:unary tail:(_ ('->'/'|>') _nl chained:chainedFunction {return chained})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(element.fnName, [result, ...element.args])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
chainedFunction
|
||||||
|
= fn:identifier '(' _nl args:array_functionArguments _nl ')'
|
||||||
|
{ return {fnName: fn.value, args: args}}
|
||||||
|
/ fn:identifier '(' _nl ')'
|
||||||
|
{ return {fnName: fn.value, args: []}}
|
||||||
|
/ fn:identifier
|
||||||
|
{ return {fnName: fn.value, args: []}}
|
||||||
|
|
||||||
|
// end of binary operators
|
||||||
|
|
||||||
|
unary
|
||||||
|
= unaryOperator:unaryOperator _nl right:(unary/postOperator)
|
||||||
|
{ return makeFunctionCall(unaryToFunction[unaryOperator], [right])}
|
||||||
|
/ postOperator
|
||||||
|
|
||||||
|
unaryOperator
|
||||||
|
= ('-' / '.-' / '!' )
|
||||||
|
|
||||||
|
postOperator = indexedValue
|
||||||
|
|
||||||
|
indexedValue
|
||||||
|
= collectionElement
|
||||||
|
/ maybeRecordElement
|
||||||
|
|
||||||
|
collectionElement
|
||||||
|
= head:maybeRecordElement &('['/'(')
|
||||||
|
tail:(
|
||||||
|
_ '[' _nl arg:expression _nl ']' {return {fn: postOperatorToFunction['[]'], args: [arg]}}
|
||||||
|
/ _ '(' _nl args:array_functionArguments _nl ')' {return {fn: postOperatorToFunction['()'], args: args}}
|
||||||
|
)*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(element.fn, [result, ...element.args])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
array_functionArguments
|
||||||
|
= head:expression tail:(_ ',' _nl @expression)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
maybeRecordElement
|
||||||
|
= recordElement
|
||||||
|
/ atom
|
||||||
|
|
||||||
|
recordElement
|
||||||
|
= head:identifier &'.'
|
||||||
|
tail:(_ '.' _nl arg:$identifier {return {fn: postOperatorToFunction['.'], args: [nodeString(arg)]}})*
|
||||||
|
{ return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(element.fn, [result, ...element.args])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
|
atom
|
||||||
|
= '(' _nl expression:expression _nl ')' {return expression}
|
||||||
|
/ basicValue
|
||||||
|
|
||||||
|
basicValue = valueConstructor / basicLiteral
|
||||||
|
|
||||||
|
basicLiteral
|
||||||
|
= string
|
||||||
|
/ float
|
||||||
|
/ integer
|
||||||
|
/ boolean
|
||||||
|
/ identifier
|
||||||
|
|
||||||
|
identifier 'identifier'
|
||||||
|
= identifier:([_a-z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
|
||||||
|
|
||||||
|
string 'string'
|
||||||
|
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
|
||||||
|
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
|
||||||
|
|
||||||
|
integer 'integer'
|
||||||
|
= digits:[0-9]+![.]
|
||||||
|
{ return nodeInteger(parseInt(text()))}
|
||||||
|
|
||||||
|
float 'float'
|
||||||
|
= ([0-9]+[.][0-9]*)
|
||||||
|
{ return nodeFloat(parseFloat(text()))}
|
||||||
|
|
||||||
|
boolean 'boolean'
|
||||||
|
= ('true'/'false')
|
||||||
|
{ return nodeBoolean(text() === 'true')}
|
||||||
|
|
||||||
|
valueConstructor
|
||||||
|
= recordConstructor
|
||||||
|
/ arrayConstructor
|
||||||
|
/ lambda
|
||||||
|
|
||||||
|
lambda
|
||||||
|
= '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}'
|
||||||
|
{ statements.push(finalExpression)
|
||||||
|
return nodeLambda(args, nodeBlock(statements)) }
|
||||||
|
/ '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}'
|
||||||
|
{ return nodeLambda(args, nodeBlock([finalExpression])) }
|
||||||
|
|
||||||
|
arrayConstructor 'array'
|
||||||
|
= '[' _nl ']'
|
||||||
|
{ return makeFunctionCall('$constructArray', [nodeExpression([])])}
|
||||||
|
/ '[' _nl args:array_elements _nl ']'
|
||||||
|
{ return makeFunctionCall('$constructArray', [nodeExpression(args)])}
|
||||||
|
|
||||||
|
array_elements
|
||||||
|
= head:expression tail:(_ ',' _nl @expression)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
recordConstructor 'record'
|
||||||
|
= '{' _nl args:array_recordArguments _nl '}'
|
||||||
|
{ return makeFunctionCall('$constructRecord', [nodeExpression(args)])}
|
||||||
|
|
||||||
|
array_recordArguments
|
||||||
|
= head:keyValuePair tail:(_ ',' _nl @keyValuePair)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
keyValuePair
|
||||||
|
= key:expression _ ':' _nl value:expression
|
||||||
|
{ return nodeKeyValue(key, value)}
|
||||||
|
|
||||||
|
_ 'optional whitespace'
|
||||||
|
= whiteSpaceCharactersOrComment*
|
||||||
|
|
||||||
|
_nl 'optional whitespace or newline'
|
||||||
|
= (whiteSpaceCharactersOrComment / commentOrNewLine)*
|
||||||
|
|
||||||
|
__ 'whitespace'
|
||||||
|
= whiteSpaceCharactersOrComment+
|
||||||
|
|
||||||
|
__nl 'whitespace or newline'
|
||||||
|
= (whiteSpaceCharactersOrComment / commentOrNewLine )+
|
||||||
|
|
||||||
|
statementSeparator 'statement separator'
|
||||||
|
= _ (';'/ commentOrNewLine)+ _
|
||||||
|
|
||||||
|
commentOrNewLine = finalComment? newLine
|
||||||
|
|
||||||
|
finalComment "line comment"
|
||||||
|
= _ ('//'/'#') @([^\r\n]*)
|
||||||
|
|
||||||
|
whiteSpaceCharactersOrComment = whiteSpaceCharacters / delimitedComment
|
||||||
|
|
||||||
|
delimitedComment "comment"
|
||||||
|
= '/*' @([^*]*) '*/'
|
||||||
|
|
||||||
|
whiteSpaceCharacters = [ \t]
|
||||||
|
|
||||||
|
newLine "newline"
|
||||||
|
= [\n\r]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
type node = {"type": string}
|
||||||
|
|
||||||
|
@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse"
|
||||||
|
|
||||||
|
let parse = (expr: string): result<node, errorValue> =>
|
||||||
|
try {
|
||||||
|
Ok(parse__(expr))
|
||||||
|
} catch {
|
||||||
|
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeBlock = {...node, "statements": array<node>}
|
||||||
|
type nodeBoolean = {...node, "value": bool}
|
||||||
|
type nodeCallIdentifier = {...node, "value": string}
|
||||||
|
type nodeExpression = {...node, "nodes": array<node>}
|
||||||
|
type nodeFloat = {...node, "value": float}
|
||||||
|
type nodeIdentifier = {...node, "value": string}
|
||||||
|
type nodeInteger = {...node, "value": int}
|
||||||
|
type nodeKeyValue = {...node, "key": node, "value": node}
|
||||||
|
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
|
||||||
|
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
|
||||||
|
type nodeString = {...node, "value": string}
|
||||||
|
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
||||||
|
|
||||||
|
type peggyNode =
|
||||||
|
| PgNodeBlock(nodeBlock)
|
||||||
|
| PgNodeBoolean(nodeBoolean)
|
||||||
|
| PgNodeCallIdentifier(nodeCallIdentifier)
|
||||||
|
| PgNodeExpression(nodeExpression)
|
||||||
|
| PgNodeFloat(nodeFloat)
|
||||||
|
| PgNodeIdentifier(nodeIdentifier)
|
||||||
|
| PgNodeInteger(nodeInteger)
|
||||||
|
| PgNodeKeyValue(nodeKeyValue)
|
||||||
|
| PgNodeLambda(nodeLambda)
|
||||||
|
| PgNodeLetStatement(nodeLetStatement)
|
||||||
|
| PgNodeString(nodeString)
|
||||||
|
| PgNodeTernary(nodeTernary)
|
||||||
|
|
||||||
|
external castNodeBlock: node => nodeBlock = "%identity"
|
||||||
|
external castNodeBoolean: node => nodeBoolean = "%identity"
|
||||||
|
external castNodeCallIdentifier: node => nodeCallIdentifier = "%identity"
|
||||||
|
external castNodeExpression: node => nodeExpression = "%identity"
|
||||||
|
external castNodeFloat: node => nodeFloat = "%identity"
|
||||||
|
external castNodeIdentifier: node => nodeIdentifier = "%identity"
|
||||||
|
external castNodeInteger: node => nodeInteger = "%identity"
|
||||||
|
external castNodeKeyValue: node => nodeKeyValue = "%identity"
|
||||||
|
external castNodeLambda: node => nodeLambda = "%identity"
|
||||||
|
external castNodeLetStatement: node => nodeLetStatement = "%identity"
|
||||||
|
external castNodeString: node => nodeString = "%identity"
|
||||||
|
external castNodeTernary: node => nodeTernary = "%identity"
|
||||||
|
|
||||||
|
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
||||||
|
let castNodeType = (node: node) =>
|
||||||
|
switch node["type"] {
|
||||||
|
| "Block" => node->castNodeBlock->PgNodeBlock
|
||||||
|
| "Boolean" => node->castNodeBoolean->PgNodeBoolean
|
||||||
|
| "CallIdentifier" => node->castNodeCallIdentifier->PgNodeCallIdentifier
|
||||||
|
| "Expression" => node->castNodeExpression->PgNodeExpression
|
||||||
|
| "Float" => node->castNodeFloat->PgNodeFloat
|
||||||
|
| "Identifier" => node->castNodeIdentifier->PgNodeIdentifier
|
||||||
|
| "Integer" => node->castNodeInteger->PgNodeInteger
|
||||||
|
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue
|
||||||
|
| "Lambda" => node->castNodeLambda->PgNodeLambda
|
||||||
|
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement
|
||||||
|
| "String" => node->castNodeString->PgNodeString
|
||||||
|
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
||||||
|
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec pgToString = (peggyNode: peggyNode): string => {
|
||||||
|
let argsToString = (args: array<nodeIdentifier>): string =>
|
||||||
|
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
|
||||||
|
|
||||||
|
let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string =>
|
||||||
|
nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("")
|
||||||
|
|
||||||
|
switch peggyNode {
|
||||||
|
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"
|
||||||
|
| PgNodeBoolean(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeCallIdentifier(node) => `::${Js.String.make(node["value"])}` // This is an identifier also but for function names
|
||||||
|
| PgNodeExpression(node) => "(" ++ node["nodes"]->nodesToStringUsingSeparator(" ") ++ ")"
|
||||||
|
| PgNodeFloat(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeIdentifier(node) => `:${node["value"]}`
|
||||||
|
| PgNodeInteger(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"])
|
||||||
|
| PgNodeLambda(node) =>
|
||||||
|
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
|
||||||
|
| PgNodeLetStatement(node) =>
|
||||||
|
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
|
||||||
|
| PgNodeString(node) => `'${node["value"]->Js.String.make}'`
|
||||||
|
| PgNodeTernary(node) =>
|
||||||
|
"(::$$ternary " ++
|
||||||
|
toString(node["condition"]) ++
|
||||||
|
" " ++
|
||||||
|
toString(node["trueExpression"]) ++
|
||||||
|
" " ++
|
||||||
|
toString(node["falseExpression"]) ++ ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
and toString = (node: node): string => node->castNodeType->pgToString
|
||||||
|
|
||||||
|
let toStringResult = (rNode: result<node, errorValue>): string =>
|
||||||
|
switch rNode {
|
||||||
|
| Ok(node) => toString(node)
|
||||||
|
| Error(error) => `Error(${errorToString(error)})`
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||||
|
module ExpressionT = Reducer_Expression_T
|
||||||
|
module Parse = Reducer_Peggy_Parse
|
||||||
|
|
||||||
|
type expression = ExpressionT.expression
|
||||||
|
|
||||||
|
let rec fromNode = (node: Parse.node): expression => {
|
||||||
|
|
||||||
|
let caseBlock = nodeBlock =>
|
||||||
|
ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)->Belt.List.fromArray)
|
||||||
|
|
||||||
|
let caseLambda = (nodeLambda: Parse.nodeLambda): expression => {
|
||||||
|
let args =
|
||||||
|
nodeLambda["args"]
|
||||||
|
->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"])
|
||||||
|
->ExpressionBuilder.eArrayString
|
||||||
|
let body = nodeLambda["body"]->caseBlock
|
||||||
|
ExpressionBuilder.eFunction("$$lambda", list{args, body})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch Parse.castNodeType(node) {
|
||||||
|
| PgNodeBlock(nodeBlock) => caseBlock(nodeBlock)
|
||||||
|
| PgNodeBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"])
|
||||||
|
| PgNodeCallIdentifier(nodeCallIdentifier) => ExpressionBuilder.eCall(nodeCallIdentifier["value"])
|
||||||
|
| PgNodeExpression(nodeExpression) =>
|
||||||
|
ExpressionT.EList(nodeExpression["nodes"]->Js.Array2.map(fromNode)->Belt.List.fromArray)
|
||||||
|
| PgNodeFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"])
|
||||||
|
| PgNodeIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"])
|
||||||
|
| PgNodeInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"]))
|
||||||
|
| PgNodeKeyValue(nodeKeyValue) =>
|
||||||
|
ExpressionT.EList(list{fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])})
|
||||||
|
| PgNodeLambda(nodeLambda) => caseLambda(nodeLambda)
|
||||||
|
| PgNodeLetStatement(nodeLetStatement) =>
|
||||||
|
ExpressionBuilder.eLetStatement(
|
||||||
|
nodeLetStatement["variable"]["value"],
|
||||||
|
fromNode(nodeLetStatement["value"]),
|
||||||
|
)
|
||||||
|
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
|
||||||
|
| PgNodeTernary(nodeTernary) =>
|
||||||
|
ExpressionBuilder.eFunction(
|
||||||
|
"$$ternary",
|
||||||
|
list{
|
||||||
|
fromNode(nodeTernary["condition"]),
|
||||||
|
fromNode(nodeTernary["trueExpression"]),
|
||||||
|
fromNode(nodeTernary["falseExpression"]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,6 +87,12 @@ let toStringResult = x =>
|
||||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toStringResultOkless = codeResult =>
|
||||||
|
switch codeResult {
|
||||||
|
| Ok(a) => toString(a)
|
||||||
|
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||||
|
}
|
||||||
|
|
||||||
let toStringResultRecord = x =>
|
let toStringResultRecord = x =>
|
||||||
switch x {
|
switch x {
|
||||||
| Ok(a) => `Ok(${toStringRecord(a)})`
|
| Ok(a) => `Ok(${toStringRecord(a)})`
|
||||||
|
|
Loading…
Reference in New Issue
Block a user