parser
toFunction additive multiplicative compact whitespace pow relational equality boolean whitespace separator left associative operators expression not identifier function call array constructor string indexed values ident priority block outerBlock optional final expression statement separator outerBlock innerBlock better errors note xor white space and record unary minus inner/outer block statement lambda sort lambda is a value constructor lambdaCall ternary ternary basicValue cleanup quotes chained Functions dot operators unify unary operators unify unary operatos notes notes notes notes parser priorities set white space or newline defined allow newlines notes function call has become a post operator recordElement recursive index postOperatorToFunction better integer comments notes record priority comment atom finalComment generated parser type cast format initiate test file test initiated; todo nodeCall; nodeExpression callIdentifier recover extra initiate testing initial tests pass tests pass remove function node ternary test parse passed to tests pass notes sort toExpression format notes remove unused modules remove unnecessary nodeLambdaCall notes note fix construct array comment test todo elixir pipe fix toString notes initial to expression test value test parsing records records comments ternary ifthenelse inner block passed inner block lambda lambda new parser tested now test tricks ternary in expression to test lambda as argument to test lambda in structures Use peggy Parser expectEvalError macros tested remove mathjs parse reducer test comparison operator
This commit is contained in:
parent
b29ba9162f
commit
db050668d1
|
@ -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)
|
||||
|
@ -75,11 +72,17 @@ and reduceExpressionList = (
|
|||
and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
||||
expressionValue,
|
||||
'e,
|
||||
> =>
|
||||
> =>
|
||||
switch valueList {
|
||||
| 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