Merge branch 'peggy' into reducer-dev
This commit is contained in:
commit
915b49bf97
|
@ -16,33 +16,33 @@ testMacro([], exampleExpression, "Ok(1)")
|
|||
|
||||
describe("bindStatement", () => {
|
||||
// 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
|
||||
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
|
||||
// Now let's feed a binding to see what happens
|
||||
testMacro(
|
||||
[],
|
||||
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
|
||||
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
|
||||
testMacro(
|
||||
[("z", EvNumber(99.))],
|
||||
eBindStatementDefault(exampleStatementY),
|
||||
"Ok((:$setBindings {z: 99} :y 1))",
|
||||
"Ok((:$setBindings {z: 99} :y 1) context: {z: 99})",
|
||||
)
|
||||
})
|
||||
|
||||
describe("bindExpression", () => {
|
||||
// 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
|
||||
testMacro(
|
||||
[],
|
||||
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
|
||||
testMacroEval(
|
||||
|
@ -110,7 +110,7 @@ describe("block", () => {
|
|||
}),
|
||||
"Ok((:$$bindExpression (:$$block (:$let :y (:add :x 1)) :y)))",
|
||||
)
|
||||
MyOnly.testMacroEval(
|
||||
testMacroEval(
|
||||
[("x", EvNumber(1.))],
|
||||
eBlock(list{
|
||||
eBlock(list{
|
||||
|
@ -125,7 +125,7 @@ describe("block", () => {
|
|||
describe("lambda", () => {
|
||||
// assign a lambda to a variable
|
||||
let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY})
|
||||
testMacro([], lambdaExpression, "Ok(lambda(y=>internal))")
|
||||
testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
|
||||
// call a lambda
|
||||
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
|
||||
testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))")
|
||||
|
|
|
@ -22,6 +22,6 @@ describe("builtin", () => {
|
|||
describe("builtin exception", () => {
|
||||
//It's a pity that MathJs does not return error position
|
||||
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) =>
|
||||
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) =>
|
||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
||||
->ExpressionValue.toStringResult
|
||||
|
@ -29,6 +32,7 @@ let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, ans
|
|||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||
test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testEvalError = expr => test(expr, () => expectEvalError(expr))
|
||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
|
|
|
@ -2,62 +2,11 @@
|
|||
open Jest
|
||||
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", () => {
|
||||
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
||||
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", 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(
|
||||
"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(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
||||
|
@ -63,15 +63,26 @@ describe("call and bindings", () => {
|
|||
})
|
||||
|
||||
describe("function tricks", () => {
|
||||
testParseToBe(
|
||||
"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})")
|
||||
testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
|
||||
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})")
|
||||
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("myadd(x,y)=x+y; z=[add]; z[0](3,2)", "????") //TODO: to fix with new parser
|
||||
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", "Ok(lambda(x,y=>internal code))")
|
||||
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 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", () => {
|
||||
// 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)")
|
||||
|
@ -70,20 +24,21 @@ describe("eval", () => {
|
|||
})
|
||||
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)"))
|
||||
test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
|
||||
test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
|
||||
testEvalError("{a: 1}.b") // invalid syntax
|
||||
})
|
||||
|
||||
describe("multi-line", () => {
|
||||
testEvalToBe("1; 2", "Error(Assignment expected)")
|
||||
testEvalToBe("1+1; 2+1", "Error(Assignment expected)")
|
||||
testEvalError("1; 2")
|
||||
testEvalError("1+1; 2+1")
|
||||
})
|
||||
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)")
|
||||
testEvalError("1; x=1")
|
||||
testEvalError("1; 1")
|
||||
testEvalToBe("x=1; x=1", "Ok({x: 1})")
|
||||
})
|
||||
})
|
||||
|
@ -94,9 +49,9 @@ describe("test exceptions", () => {
|
|||
"javascriptraise('div by 0')",
|
||||
"Error(JS Exception: Error: 'div by 0')",
|
||||
)
|
||||
testDescriptionEvalToBe(
|
||||
"rescript exception",
|
||||
"rescriptraise()",
|
||||
"Error(TODO: unhandled rescript exception)",
|
||||
)
|
||||
// testDescriptionEvalToBe(
|
||||
// "rescript exception",
|
||||
// "rescriptraise()",
|
||||
// "Error(TODO: unhandled rescript exception)",
|
||||
// )
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"homepage": "https://squiggle-language.com",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"peggy": "peggy --cache ",
|
||||
"build": "yarn build:rescript && yarn build:typescript",
|
||||
"build:rescript": "rescript build -with-deps",
|
||||
"build:typescript": "tsc",
|
||||
|
|
|
@ -13,6 +13,8 @@ open Reducer_ErrorValue
|
|||
DO NOT try to add external function mapping here!
|
||||
*/
|
||||
|
||||
//TODO: pow to xor
|
||||
|
||||
exception TestRescriptException
|
||||
|
||||
let callInternal = (call: functionCall, environment, reducer: ExpressionT.reducerFn): result<
|
||||
|
@ -116,9 +118,9 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
}
|
||||
|
||||
switch call {
|
||||
| ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) =>
|
||||
arrayAtIndex(aValueArray, fIndex)
|
||||
| ("$atIndex", [EvRecord(dict), EvArray([EvString(sIndex)])]) => recordAtIndex(dict, sIndex)
|
||||
| ("$atIndex", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
|
||||
| ("$atIndex", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
|
||||
| ("$constructArray", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
|
||||
| ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
||||
| ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
||||
| ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
Macros are used to define language building blocks. They are like Lisp macros.
|
||||
*/
|
||||
module Bindings = Reducer_Expression_Bindings
|
||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module ExpressionWithContext = Reducer_ExpressionWithContext
|
||||
|
@ -139,7 +140,8 @@ let dispatchMacroCall = (
|
|||
bindings: ExpressionT.bindings,
|
||||
environment,
|
||||
): 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 =>
|
||||
switch conditionValue {
|
||||
| ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok
|
||||
|
|
|
@ -18,13 +18,10 @@ type internalCode = ReducerInterface_ExpressionValue.internalCode
|
|||
type t = expression
|
||||
|
||||
/*
|
||||
Converts a MathJs code to expression
|
||||
Converts a Squigle code to expression
|
||||
*/
|
||||
let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
||||
expr->parser->Result.flatMap(node => converter(node))
|
||||
|
||||
let parse = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
||||
let parse = (peggyCode: string): result<t, errorValue> =>
|
||||
peggyCode->Reducer_Peggy_Parse.parse->Result.map(node => Reducer_Peggy_ToExpression.fromNode(node))
|
||||
|
||||
/*
|
||||
Recursively evaluate/reduce the expression (Lisp AST)
|
||||
|
@ -80,6 +77,12 @@ and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
|||
| list{EvCall(fName), ...args} =>
|
||||
(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} =>
|
||||
Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression)
|
||||
| _ =>
|
||||
|
|
|
@ -42,6 +42,12 @@ let toStringResult = codeResult =>
|
|||
| 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 => {
|
||||
Js.log(toString(expr))
|
||||
expr
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
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)})`
|
||||
}
|
||||
|
||||
let toStringResultOkless = codeResult =>
|
||||
switch codeResult {
|
||||
| Ok(a) => toString(a)
|
||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
||||
let toStringResultRecord = x =>
|
||||
switch x {
|
||||
| Ok(a) => `Ok(${toStringRecord(a)})`
|
||||
|
|
Loading…
Reference in New Issue
Block a user