Merged with develop

This commit is contained in:
Ozzie Gooen 2022-05-23 13:49:39 -04:00
commit faf4718f4d
33 changed files with 2135 additions and 1496 deletions

View File

@ -8,6 +8,6 @@ updates:
- package-ecosystem: "npm" # See documentation for possible values - package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
schedule: schedule:
interval: "daily" interval: "weekly"
commit-message: commit-message:
prefix: "⬆️" prefix: "⬆️"

View File

@ -11,3 +11,4 @@ packages/squiggle-lang/.nyc_output/
packages/squiggle-lang/coverage/ packages/squiggle-lang/coverage/
packages/squiggle-lang/.cache/ packages/squiggle-lang/.cache/
packages/website/build/ packages/website/build/
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js

View File

@ -9,7 +9,7 @@
"react": "^18.1.0", "react": "^18.1.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-use": "^17.3.2", "react-use": "^17.4.0",
"react-vega": "^7.5.1", "react-vega": "^7.5.1",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"vega": "^5.22.1", "vega": "^5.22.1",
@ -18,20 +18,20 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.17.12", "@babel/plugin-proposal-private-property-in-object": "^7.17.12",
"@storybook/addon-actions": "^6.4.22", "@storybook/addon-actions": "^6.5.3",
"@storybook/addon-essentials": "^6.4.22", "@storybook/addon-essentials": "^6.5.4",
"@storybook/addon-links": "^6.4.22", "@storybook/addon-links": "^6.5.4",
"@storybook/builder-webpack5": "^6.4.22", "@storybook/builder-webpack5": "^6.5.4",
"@storybook/manager-webpack5": "^6.4.22", "@storybook/manager-webpack5": "^6.5.4",
"@storybook/node-logger": "^6.4.22", "@storybook/node-logger": "^6.5.4",
"@storybook/preset-create-react-app": "^4.1.0", "@storybook/preset-create-react-app": "^4.1.1",
"@storybook/react": "^6.4.22", "@storybook/react": "^6.5.4",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0", "@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^14.2.0", "@testing-library/user-event": "^14.2.0",
"@types/jest": "^27.5.0", "@types/jest": "^27.5.0",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.14.182",
"@types/node": "^17.0.34", "@types/node": "^17.0.35",
"@types/react": "^18.0.9", "@types/react": "^18.0.9",
"@types/react-dom": "^18.0.4", "@types/react-dom": "^18.0.4",
"@types/styled-components": "^5.1.24", "@types/styled-components": "^5.1.24",

View File

@ -21,3 +21,4 @@ dist
_coverage _coverage
coverage coverage
.nyc_output/ .nyc_output/
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js

View File

@ -4,4 +4,5 @@ lib
*.gen.tsx *.gen.tsx
.nyc_output/ .nyc_output/
coverage/ coverage/
.cache/ .cache/
Reducer_Peggy_GeneratedParser.js

View File

@ -16,33 +16,41 @@ testMacro([], exampleExpression, "Ok(1)")
describe("bindStatement", () => { describe("bindStatement", () => {
// A statement is bound by the bindings created by the previous statement // A statement is bound by the bindings created by the previous statement
testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1))") testMacro(
[],
eBindStatement(eBindings([]), exampleStatementY),
"Ok((:$_setBindings_$ {} :y 1) context: {})",
)
// Then it answers the bindings for the next statement when reduced // Then it answers the bindings for the next statement when reduced
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})") testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
// Now let's feed a binding to see what happens // Now let's feed a binding to see what happens
testMacro( testMacro(
[], [],
eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX), eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX),
"Ok((:$setBindings {x: 2} :y 2))", "Ok((:$_setBindings_$ {x: 2} :y 2) context: {x: 2})",
) )
// An expression does not return a binding, thus error // An expression does not return a binding, thus error
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Error(Assignment expected)") testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block // When bindings from previous statement are missing the context is injected. This must be the first statement of a block
testMacro( testMacro(
[("z", EvNumber(99.))], [("z", EvNumber(99.))],
eBindStatementDefault(exampleStatementY), eBindStatementDefault(exampleStatementY),
"Ok((:$setBindings {z: 99} :y 1))", "Ok((:$_setBindings_$ {z: 99} :y 1) context: {z: 99})",
) )
}) })
describe("bindExpression", () => { describe("bindExpression", () => {
// x is simply bound in the expression // x is simply bound in the expression
testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2)") testMacro(
[],
eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")),
"Ok(2 context: {x: 2})",
)
// When an let statement is the end expression then bindings are returned // When an let statement is the end expression then bindings are returned
testMacro( testMacro(
[], [],
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY), eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY),
"Ok((:$exportBindings (:$setBindings {x: 2} :y 1)))", "Ok((:$_exportBindings_$ (:$_setBindings_$ {x: 2} :y 1)) context: {x: 2})",
) )
// Now let's reduce that expression // Now let's reduce that expression
testMacroEval( testMacroEval(
@ -60,37 +68,37 @@ describe("bindExpression", () => {
describe("block", () => { describe("block", () => {
// Block with a single expression // Block with a single expression
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$bindExpression 1))") testMacro([], eBlock(list{exampleExpression}), "Ok((:$$_bindExpression_$$ 1))")
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)") testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
// Block with a single statement // Block with a single statement
testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$bindExpression (:$let :y 1)))") testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))")
testMacroEval([], eBlock(list{exampleStatementY}), "Ok({y: 1})") testMacroEval([], eBlock(list{exampleStatementY}), "Ok({y: 1})")
// Block with a statement and an expression // Block with a statement and an expression
testMacro( testMacro(
[], [],
eBlock(list{exampleStatementY, exampleExpressionY}), eBlock(list{exampleStatementY, exampleExpressionY}),
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) :y))", "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))",
) )
testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)") testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
// Block with a statement and another statement // Block with a statement and another statement
testMacro( testMacro(
[], [],
eBlock(list{exampleStatementY, exampleStatementZ}), eBlock(list{exampleStatementY, exampleStatementZ}),
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) (:$let :z :y)))", "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))",
) )
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})") testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})")
// Block inside a block // Block inside a block
testMacro( testMacro(
[], [],
eBlock(list{eBlock(list{exampleExpression})}), eBlock(list{eBlock(list{exampleExpression})}),
"Ok((:$$bindExpression (:$$block 1)))", "Ok((:$$_bindExpression_$$ (:$$_block_$$ 1)))",
) )
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)") testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
// Block assigned to a variable // Block assigned to a variable
testMacro( testMacro(
[], [],
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}), eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
"Ok((:$$bindExpression (:$let :z (:$$block (:$$block :y)))))", "Ok((:$$_bindExpression_$$ (:$_let_$ :z (:$$_block_$$ (:$$_block_$$ :y)))))",
) )
testMacroEval( testMacroEval(
[], [],
@ -99,7 +107,7 @@ describe("block", () => {
) )
// Empty block // Empty block
testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error
// :$$block (:$$block (:$let :y (:add :x 1)) :y)" // :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)"
testMacro( testMacro(
[], [],
eBlock(list{ eBlock(list{
@ -108,9 +116,9 @@ describe("block", () => {
eSymbol("y"), eSymbol("y"),
}), }),
}), }),
"Ok((:$$bindExpression (:$$block (:$let :y (:add :x 1)) :y)))", "Ok((:$$_bindExpression_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)))",
) )
MyOnly.testMacroEval( testMacroEval(
[("x", EvNumber(1.))], [("x", EvNumber(1.))],
eBlock(list{ eBlock(list{
eBlock(list{ eBlock(list{
@ -124,17 +132,17 @@ describe("block", () => {
describe("lambda", () => { describe("lambda", () => {
// assign a lambda to a variable // assign a lambda to a variable
let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY}) let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY})
testMacro([], lambdaExpression, "Ok(lambda(y=>internal))") testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
// call a lambda // call a lambda
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))") testMacro([], callLambdaExpression, "Ok(((:$$_lambda_$$ [y] :y) 1))")
testMacroEval([], callLambdaExpression, "Ok(1)") testMacroEval([], callLambdaExpression, "Ok(1)")
// Parameters shadow the outer scope // Parameters shadow the outer scope
testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(1)") testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(1)")
// When not shadowed by the parameters, the outer scope variables are available // When not shadowed by the parameters, the outer scope variables are available
let lambdaExpression = eFunction( let lambdaExpression = eFunction(
"$$lambda", "$$_lambda_$$",
list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})}, list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})},
) )
let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)}) let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)})

View File

@ -26,6 +26,16 @@ describe("builtin", () => {
describe("builtin exception", () => { describe("builtin exception", () => {
//It's a pity that MathJs does not return error position //It's a pity that MathJs does not return error position
test("MathJs Exception", () => test("MathJs Exception", () =>
expectEvalToBe("testZadanga()", "Error(JS Exception: Error: Undefined function testZadanga)") expectEvalToBe("testZadanga(1)", "Error(JS Exception: Error: Undefined function testZadanga)")
) )
}) })
describe("error reporting from collection functions", () => {
testEval("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
testEval(
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
"Error(zarathsuzaWasHere is not defined)",
)
// FIXME: returns "Error(Function not found: map(Array,Symbol))"
// Actually this error is correct but not informative
})

View File

@ -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))")
})
})

View File

@ -0,0 +1,373 @@
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("float", () => {
testParse("1.", "{1}")
testParse("1.1", "{1.1}")
testParse(".1", "{0.1}")
testParse("0.1", "{0.1}")
testParse("1e1", "{10}")
testParse("1e-1", "{0.1}")
testParse(".1e1", "{1}")
testParse("0.1e1", "{1}")
})
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))}")
testParse("!a[0]", "{(::not (::$_atIndex_$ :a 0))}")
})
describe("multiplicative", () => {
testParse("1 * 2", "{(::multiply 1 2)}")
testParse("1 / 2", "{(::divide 1 2)}")
testParse("1 * 2 * 3", "{(::multiply (::multiply 1 2) 3)}")
testParse("1 * 2 / 3", "{(::divide (::multiply 1 2) 3)}")
testParse("1 / 2 * 3", "{(::multiply (::divide 1 2) 3)}")
testParse("1 / 2 / 3", "{(::divide (::divide 1 2) 3)}")
testParse("1 * 2 + 3 * 4", "{(::add (::multiply 1 2) (::multiply 3 4))}")
testParse("1 * 2 - 3 * 4", "{(::subtract (::multiply 1 2) (::multiply 3 4))}")
testParse("1 * 2 .+ 3 * 4", "{(::dotAdd (::multiply 1 2) (::multiply 3 4))}")
testParse("1 * 2 .- 3 * 4", "{(::dotSubtract (::multiply 1 2) (::multiply 3 4))}")
testParse("1 * 2 + 3 .* 4", "{(::add (::multiply 1 2) (::dotMultiply 3 4))}")
testParse("1 * 2 + 3 / 4", "{(::add (::multiply 1 2) (::divide 3 4))}")
testParse("1 * 2 + 3 ./ 4", "{(::add (::multiply 1 2) (::dotDivide 3 4))}")
testParse("1 * 2 - 3 .* 4", "{(::subtract (::multiply 1 2) (::dotMultiply 3 4))}")
testParse("1 * 2 - 3 / 4", "{(::subtract (::multiply 1 2) (::divide 3 4))}")
testParse("1 * 2 - 3 ./ 4", "{(::subtract (::multiply 1 2) (::dotDivide 3 4))}")
testParse("1 * 2 - 3 * 4^5", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5)))}")
testParse(
"1 * 2 - 3 * 4^5^6",
"{(::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6)))}",
)
testParse("1 * -a[-2]", "{(::multiply 1 (::unaryMinus (::$_atIndex_$ :a (::unaryMinus 2))))}")
})
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("post operators", () => {
//function call, array and record access are post operators with higher priority than unary operators
testParse("a==!b(1)", "{(::equal :a (::not (::b 1)))}")
testParse("a==!b[1]", "{(::equal :a (::not (::$_atIndex_$ :b 1)))}")
testParse("a==!b.one", "{(::equal :a (::not (::$_atIndex_$ :b 'one')))}")
})
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}")
testParse(
`
/* This is
a multi line
comment */
1`,
"{1}",
)
})
describe("ternary operator", () => {
testParse("true ? 2 : 3", "{(::$$_ternary_$$ true 2 3)}")
testParse(
"false ? 2 : false ? 4 : 5",
"{(::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5))}",
) // nested ternary
})
describe("if then else", () => {
testParse("if true then 2 else 3", "{(::$$_ternary_$$ true {2} {3})}")
testParse("if false then {2} else {3}", "{(::$$_ternary_$$ false {2} {3})}")
testParse(
"if false then {2} else if false then {4} else {5}",
"{(::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5}))}",
) //nested if
})
describe("logical", () => {
testParse("true || false", "{(::or true false)}")
testParse("true && false", "{(::and true false)}")
testParse("a * b + c", "{(::add (::multiply :a :b) :c)}") // for comparison
testParse("a && b || c", "{(::or (::and :a :b) :c)}")
testParse("a && b || c && d", "{(::or (::and :a :b) (::and :c :d))}")
testParse("a && !b || c", "{(::or (::and :a (::not :b)) :c)}")
testParse("a && b==c || d", "{(::or (::and :a (::equal :b :c)) :d)}")
testParse("a && b!=c || d", "{(::or (::and :a (::unequal :b :c)) :d)}")
testParse("a && !(b==c) || d", "{(::or (::and :a (::not (::equal :b :c))) :d)}")
testParse("a && b>=c || d", "{(::or (::and :a (::largerEq :b :c)) :d)}")
testParse("a && !(b>=c) || d", "{(::or (::and :a (::not (::largerEq :b :c))) :d)}")
testParse("a && b<=c || d", "{(::or (::and :a (::smallerEq :b :c)) :d)}")
testParse("a && b>c || d", "{(::or (::and :a (::larger :b :c)) :d)}")
testParse("a && b<c || d", "{(::or (::and :a (::smaller :b :c)) :d)}")
testParse("a && b<c[i] || d", "{(::or (::and :a (::smaller :b (::$_atIndex_$ :c :i))) :d)}")
testParse("a && b<c.i || d", "{(::or (::and :a (::smaller :b (::$_atIndex_$ :c 'i'))) :d)}")
testParse("a && b<c(i) || d", "{(::or (::and :a (::smaller :b (::c :i))) :d)}")
testParse("a && b<1+2 || d", "{(::or (::and :a (::smaller :b (::add 1 2))) :d)}")
testParse(
"a && b<1+2*3 || d",
"{(::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d)}",
)
testParse(
"a && b<1+2*-3+4 || d",
"{(::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d)}",
)
testParse(
"a && b<1+2*3 || d ? true : false",
"{(::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false)}",
)
})
describe("pipe", () => {
testParse("1 -> add(2)", "{(::add 1 2)}")
testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}")
testParse("-a[1] -> add(2)", "{(::add (::unaryMinus (::$_atIndex_$ :a 1)) 2)}")
testParse("-f(1) -> add(2)", "{(::add (::unaryMinus (::f 1)) 2)}")
testParse("1 + 2 -> add(3)", "{(::add 1 (::add 2 3))}")
testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}")
testParse("1 -> subtract(2)", "{(::subtract 1 2)}")
testParse("-1 -> subtract(2)", "{(::subtract (::unaryMinus 1) 2)}")
testParse("1 -> subtract(2) * 3", "{(::multiply (::subtract 1 2) 3)}")
})
describe("elixir pipe", () => {
//handled together with -> so there is no need for seperate tests
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}",
)
testParse("f({|x| x+1})", "{(::f {|:x| {(::add :x 1)}})}")
testParse("map(arr, {|x| x+1})", "{(::map :arr {|:x| {(::add :x 1)}})}")
testParse(
"map([1,2,3], {|x| x+1})",
"{(::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}",
)
testParse(
"[1,2,3]->map({|x| x+1})",
"{(::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}",
)
})
})
describe("parsing new line", () => {
testParse(
`
a +
b`,
"{(::add :a :b)}",
)
testParse(
`
x=
1`,
"{:x = {1}}",
)
testParse(
`
x=1
y=2`,
"{:x = {1}; :y = {2}}",
)
testParse(
`
x={
y=2;
y }
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x={
y=2
y }
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x={
y=2
y
}
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x=1
y=2
z=3
`,
"{:x = {1}; :y = {2}; :z = {3}}",
)
testParse(
`
f={
x=1
y=2
z=3
x+y+z
}
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}",
)
testParse(
`
f={
x=1
y=2
z=3
x+y+z
}
g=f+4
g
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; :g}",
)
testParse(
`
f =
{
x=1; //x
y=2 //y
z=
3
x+
y+
z
}
g =
f +
4
g ->
h ->
p ->
q
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::q (::p (::h :g)))}",
)
testParse(
`
a |>
b |>
c |>
d
`,
"{(::d (::c (::b :a)))}",
)
testParse(
`
a |>
b |>
c |>
d +
e
`,
"{(::add (::d (::c (::b :a))) :e)}",
)
})

View File

@ -0,0 +1,220 @@
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, ()))
module MySkip = {
let testToExpression = (expr, answer, ~v="_", ()) =>
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
}
module MyOnly = {
let testToExpression = (expr, answer, ~v="_", ()) =>
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~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
testToExpression(
"f(x) = x> 2 ? 0 : 1; f(3)",
"(:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ (:$$_ternary_$$ (:larger :x 2) 0 1)))) (:f 3))",
~v="0",
(),
)
})
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)}",
(),
)
})
})

View File

@ -19,6 +19,9 @@ let expectParseToBe = (expr: string, answer: string) =>
let expectEvalToBe = (expr: string, answer: string) => let expectEvalToBe = (expr: string, answer: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
let expectEvalError = (expr: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None) Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
->ExpressionValue.toStringResult ->ExpressionValue.toStringResult
@ -29,6 +32,7 @@ let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, ans
let testDescriptionParseToBe = (desc, expr, answer) => let testDescriptionParseToBe = (desc, expr, answer) =>
test(desc, () => expectParseToBe(expr, answer)) test(desc, () => expectParseToBe(expr, answer))
let testEvalError = expr => test(expr, () => expectEvalError(expr))
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer)) let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
let testEvalBindingsToBe = (expr, bindingsList, answer) => let testEvalBindingsToBe = (expr, bindingsList, answer) =>

View File

@ -2,62 +2,10 @@
open Jest open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
// describe("Parse for Bindings", () => {
// testParseOuterToBe("x", "Ok((:$$bindExpression (:$$bindings) :x))")
// testParseOuterToBe("x+1", "Ok((:$$bindExpression (:$$bindings) (:add :x 1)))")
// testParseOuterToBe(
// "y = x+1; y",
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))",
// )
// })
// describe("Parse Partial", () => {
// testParsePartialToBe(
// "x",
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))",
// )
// testParsePartialToBe(
// "y=x",
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y :x)) (:$exportVariablesExpression)))",
// )
// testParsePartialToBe(
// "y=x+1",
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$exportVariablesExpression)))",
// )
// testParsePartialToBe(
// "y = x+1; z = y",
// "Ok((:$$bindExpression (:$$bindStatement (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$let :z :y)) (:$exportVariablesExpression)))",
// )
// })
describe("Eval with Bindings", () => { describe("Eval with Bindings", () => {
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)") testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)") testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
testParseToBe("y = x+1; y", "Ok((:$$block (:$$block (:$let :y (:add :x 1)) :y)))") testParseToBe("y = x+1; y", "Ok((:$$_block_$$ (:$_let_$ :y (:$$_block_$$ (:add :x 1))) :y))")
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)") testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})") testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
}) })
/*
Partial code is a partial code fragment that is cut out from a larger code.
Therefore it does not end with an expression.
*/
// describe("Eval Partial", () => {
// testEvalPartialBindingsToBe(
// // A partial cannot end with an expression
// "x",
// list{("x", ExpressionValue.EvNumber(1.))},
// "Error(Assignment expected)",
// )
// testEvalPartialBindingsToBe("y=x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 1})")
// testEvalPartialBindingsToBe(
// "y=x+1",
// list{("x", ExpressionValue.EvNumber(1.))},
// "Ok({x: 1,y: 2})",
// )
// testEvalPartialBindingsToBe(
// "y = x+1; z = y",
// list{("x", ExpressionValue.EvNumber(1.))},
// "Ok({x: 1,y: 2,z: 2})",
// )
// })

View File

@ -2,8 +2,11 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("Parse function assignment", () => { describe("Parse function assignment", () => {
testParseToBe("f(x)=x", "Ok((:$$block (:$let :f (:$$lambda [x] (:$$block :x)))))") testParseToBe("f(x)=x", "Ok((:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ :x)))))")
testParseToBe("f(x)=2*x", "Ok((:$$block (:$let :f (:$$lambda [x] (:$$block (:multiply 2 :x))))))") testParseToBe(
"f(x)=2*x",
"Ok((:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ (:multiply 2 :x))))))",
)
//MathJs does not allow blocks in function definitions //MathJs does not allow blocks in function definitions
}) })

View File

@ -25,7 +25,7 @@ describe("Arity check", () => {
) )
testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))") testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))")
testEvalToBe( testEvalToBe(
"f(x,y)=x(y); f(z)", "f(x,y)=x(y); f(1)",
"Error(2 arguments expected. Instead 1 argument(s) were passed.)", "Error(2 arguments expected. Instead 1 argument(s) were passed.)",
) )
}) })
@ -51,7 +51,7 @@ describe("call and bindings", () => {
) )
testParseToBe( testParseToBe(
"f=99; g(x)=f; g(2)", "f=99; g(x)=f; g(2)",
"Ok((:$$block (:$$block (:$let :f 99) (:$let :g (:$$lambda [x] (:$$block :f))) (:g 2))))", "Ok((:$$_block_$$ (:$_let_$ :f (:$$_block_$$ 99)) (:$_let_$ :g (:$$_lambda_$$ [x] (:$$_block_$$ :f))) (:g 2)))",
) )
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)") testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
@ -63,15 +63,28 @@ describe("call and bindings", () => {
}) })
describe("function tricks", () => { describe("function tricks", () => {
testParseToBe( testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
"f(x)=f(y)=2; f(2)",
"Ok((:$$block (:$$block (:$let :f (:$$lambda [x] (:$$block (:$let :f (:$$lambda [y] (:$$block 2)))))) (:f 2))))",
)
testEvalToBe("f(x)=f(y)=2; f(2)", "Ok({f: lambda(y=>internal code),x: 2})")
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)") testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})") testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})")
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout? MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
MySkip.testEvalToBe("myadd(x,y)=x+y; z=[add]; z[0](3,2)", "????") //TODO: to fix with new parser testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
MySkip.testEvalToBe("myaddd(x,y)=x+y; z={x: add}; z.x(3,2)", "????") //TODO: to fix with new parser testEvalToBe("myadd(x,y)=x+y; z=myadd; z(1, 1)", "Ok(2)")
})
describe("lambda in structures", () => {
testEvalToBe(
"myadd(x,y)=x+y; z=[myadd]",
"Ok({myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})",
)
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x(3,2)", "Ok(5)")
})
describe("ternary and bindings", () => {
testEvalToBe("f(x)=x ? 1 : 0; f(true)", "Ok(1)")
testEvalToBe("f(x)=x>2 ? 1 : 0; f(3)", "Ok(1)")
}) })

View File

@ -2,7 +2,7 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("Parse ternary operator", () => { describe("Parse ternary operator", () => {
testParseToBe("true ? 'YES' : 'NO'", "Ok((:$$block (:$$ternary true 'YES' 'NO')))") testParseToBe("true ? 'YES' : 'NO'", "Ok((:$$_block_$$ (:$$_ternary_$$ true 'YES' 'NO')))")
}) })
describe("Evaluate ternary operator", () => { describe("Evaluate ternary operator", () => {

View File

@ -1,55 +1,9 @@
open Jest open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("reducer using mathjs parse", () => {
// Test the MathJs parser compatibility
// Those tests toString that there is a semantic mapping from MathJs to Expression
// Reducer.parse is called by Reducer.eval
// See https://mathjs.org/docs/expressions/syntax.html
// See https://mathjs.org/docs/reference/functions.html
// Those tests toString that we are converting mathjs parse tree to what we need
describe("expressions", () => {
testParseToBe("1", "Ok((:$$block 1))")
testParseToBe("(1)", "Ok((:$$block 1))")
testParseToBe("1+2", "Ok((:$$block (:add 1 2)))")
testParseToBe("1+2*3", "Ok((:$$block (:add 1 (:multiply 2 3))))")
})
describe("arrays", () => {
//Note. () is a empty list in Lisp
// The only builtin structure in Lisp is list. There are no arrays
// [1,2,3] becomes (1 2 3)
testDescriptionParseToBe("empty", "[]", "Ok((:$$block ()))")
testParseToBe("[1, 2, 3]", "Ok((:$$block (1 2 3)))")
testParseToBe("['hello', 'world']", "Ok((:$$block ('hello' 'world')))")
testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$$block (:$atIndex (0 1 2) (1))))")
})
describe("records", () => {
testDescriptionParseToBe(
"define",
"{a: 1, b: 2}",
"Ok((:$$block (:$constructRecord (('a' 1) ('b' 2)))))",
)
testDescriptionParseToBe(
"use",
"{a: 1, b: 2}.a",
"Ok((:$$block (:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a'))))",
)
})
describe("multi-line", () => {
testParseToBe("1; 2", "Ok((:$$block (:$$block 1 2)))")
testParseToBe("1+1; 2+1", "Ok((:$$block (:$$block (:add 1 1) (:add 2 1))))")
})
describe("assignment", () => {
testParseToBe("x=1; x", "Ok((:$$block (:$$block (:$let :x 1) :x)))")
testParseToBe("x=1+1; x+1", "Ok((:$$block (:$$block (:$let :x (:add 1 1)) (:add :x 1))))")
})
})
describe("eval", () => { describe("eval", () => {
// All MathJs operators and functions are builtin for string, float and boolean // All MathJs operators and functions are builtin for string, float and boolean
// .e.g + - / * > >= < <= == /= not and or // .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/expressions/syntax.html
// See https://mathjs.org/docs/reference/functions.html // See https://mathjs.org/docs/reference/functions.html
describe("expressions", () => { describe("expressions", () => {
testEvalToBe("1", "Ok(1)") testEvalToBe("1", "Ok(1)")
@ -70,20 +24,21 @@ describe("eval", () => {
}) })
describe("records", () => { describe("records", () => {
test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1,b: 2})")) test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1,b: 2})"))
test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)")) test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)")) test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
testEvalError("{a: 1}.b") // invalid syntax
}) })
describe("multi-line", () => { describe("multi-line", () => {
testEvalToBe("1; 2", "Error(Assignment expected)") testEvalError("1; 2")
testEvalToBe("1+1; 2+1", "Error(Assignment expected)") testEvalError("1+1; 2+1")
}) })
describe("assignment", () => { describe("assignment", () => {
testEvalToBe("x=1; x", "Ok(1)") testEvalToBe("x=1; x", "Ok(1)")
testEvalToBe("x=1+1; x+1", "Ok(3)") testEvalToBe("x=1+1; x+1", "Ok(3)")
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
testEvalToBe("1; x=1", "Error(Assignment expected)") testEvalError("1; x=1")
testEvalToBe("1; 1", "Error(Assignment expected)") testEvalError("1; 1")
testEvalToBe("x=1; x=1", "Ok({x: 1})") testEvalToBe("x=1; x=1", "Ok({x: 1})")
}) })
}) })
@ -94,9 +49,9 @@ describe("test exceptions", () => {
"javascriptraise('div by 0')", "javascriptraise('div by 0')",
"Error(JS Exception: Error: 'div by 0')", "Error(JS Exception: Error: 'div by 0')",
) )
testDescriptionEvalToBe( // testDescriptionEvalToBe(
"rescript exception", // "rescript exception",
"rescriptraise()", // "rescriptraise()",
"Error(TODO: unhandled rescript exception)", // "Error(TODO: unhandled rescript exception)",
) // )
}) })

View File

@ -123,34 +123,37 @@ describe("eval on distribution functions", () => {
describe("parse on distribution functions", () => { describe("parse on distribution functions", () => {
describe("power", () => { describe("power", () => {
testParse("normal(5,2) ^ normal(5,1)", "Ok((:$$block (:pow (:normal 5 2) (:normal 5 1))))") testParse("normal(5,2) ^ normal(5,1)", "Ok((:$$_block_$$ (:pow (:normal 5 2) (:normal 5 1))))")
testParse("3 ^ normal(5,1)", "Ok((:$$block (:pow 3 (:normal 5 1))))") testParse("3 ^ normal(5,1)", "Ok((:$$_block_$$ (:pow 3 (:normal 5 1))))")
testParse("normal(5,2) ^ 3", "Ok((:$$block (:pow (:normal 5 2) 3)))") testParse("normal(5,2) ^ 3", "Ok((:$$_block_$$ (:pow (:normal 5 2) 3)))")
}) })
describe("subtraction", () => { describe("subtraction", () => {
testParse("10 - normal(5,1)", "Ok((:$$block (:subtract 10 (:normal 5 1))))") testParse("10 - normal(5,1)", "Ok((:$$_block_$$ (:subtract 10 (:normal 5 1))))")
testParse("normal(5,1) - 10", "Ok((:$$block (:subtract (:normal 5 1) 10)))") testParse("normal(5,1) - 10", "Ok((:$$_block_$$ (:subtract (:normal 5 1) 10)))")
}) })
describe("pointwise arithmetic expressions", () => { describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse( testParse(
~skip=true, ~skip=true,
"normal(5,2) .- normal(5,1)", "normal(5,2) .- normal(5,1)",
"Ok((:$$block (:dotSubtract (:normal 5 2) (:normal 5 1))))", "Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))",
// TODO: !!! returns "Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))" // TODO: !!! returns "Ok((:$$_block_$$ (:dotPow (:normal 5 2) (:normal 5 1))))"
) )
testParse( testParse(
"normal(5,2) .* normal(5,1)", "normal(5,2) .* normal(5,1)",
"Ok((:$$block (:dotMultiply (:normal 5 2) (:normal 5 1))))", "Ok((:$$_block_$$ (:dotMultiply (:normal 5 2) (:normal 5 1))))",
) )
testParse( testParse(
"normal(5,2) ./ normal(5,1)", "normal(5,2) ./ normal(5,1)",
"Ok((:$$block (:dotDivide (:normal 5 2) (:normal 5 1))))", "Ok((:$$_block_$$ (:dotDivide (:normal 5 2) (:normal 5 1))))",
)
testParse(
"normal(5,2) .^ normal(5,1)",
"Ok((:$$_block_$$ (:dotPow (:normal 5 2) (:normal 5 1))))",
) )
testParse("normal(5,2) .^ normal(5,1)", "Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))")
}) })
describe("equality", () => { describe("equality", () => {
testParse("5 == normal(5,2)", "Ok((:$$block (:equal 5 (:normal 5 2))))") testParse("5 == normal(5,2)", "Ok((:$$_block_$$ (:equal 5 (:normal 5 2))))")
}) })
describe("pointwise adding two normals", () => { describe("pointwise adding two normals", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")

View File

@ -4,7 +4,9 @@
"homepage": "https://squiggle-language.com", "homepage": "https://squiggle-language.com",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "yarn build:rescript && yarn build:typescript", "peggy": "peggy --cache",
"build": "yarn build:peggy && yarn build:rescript && yarn build:typescript",
"build:peggy": "find . -type f -name *.peggy -exec yarn peggy {} \\;",
"build:rescript": "rescript build -with-deps", "build:rescript": "rescript build -with-deps",
"build:typescript": "tsc", "build:typescript": "tsc",
"bundle": "webpack", "bundle": "webpack",
@ -54,11 +56,12 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moduleserve": "^0.9.1", "moduleserve": "^0.9.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"peggy": "^1.2.0",
"reanalyze": "^2.19.0", "reanalyze": "^2.19.0",
"rescript-fast-check": "^1.1.1", "rescript-fast-check": "^1.1.1",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"ts-node": "^10.7.0", "ts-node": "^10.8.0",
"typescript": "^4.6.3", "typescript": "^4.6.3",
"webpack": "^5.72.1", "webpack": "^5.72.1",
"webpack-cli": "^4.9.2" "webpack-cli": "^4.9.2"

View File

@ -44,6 +44,9 @@ let registry = [
), ),
Function.make( Function.make(
~name="To", ~name="To",
~definitions=[TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make))], ~definitions=[TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
TwoArgDist.make("credibleIntervalToDistribution", twoArgs(SymbolicDist.From90thPercentile.make))
],
), ),
] ]

View File

@ -13,6 +13,8 @@ open Reducer_ErrorValue
DO NOT try to add external function mapping here! DO NOT try to add external function mapping here!
*/ */
//TODO: pow to xor
exception TestRescriptException exception TestRescriptException
let callInternal = (call: functionCall, environment, reducer: ExpressionT.reducerFn): result< let callInternal = (call: functionCall, environment, reducer: ExpressionT.reducerFn): result<
@ -128,12 +130,12 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
} }
switch call { switch call {
| ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) => | ("$_atIndex_$", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
arrayAtIndex(aValueArray, fIndex) | ("$_atIndex_$", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
| ("$atIndex", [EvRecord(dict), EvArray([EvString(sIndex)])]) => recordAtIndex(dict, sIndex) | ("$_constructArray_$", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
| ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs) | ("$_constructRecord_$", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
| ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings) | ("$_exportBindings_$", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
| ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) => | ("$_setBindings_$", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
doSetBindings(externalBindings, symbol, value) doSetBindings(externalBindings, symbol, value)
| ("inspect", [value, EvString(label)]) => inspectLabel(value, label) | ("inspect", [value, EvString(label)]) => inspectLabel(value, label)
| ("inspect", [value]) => inspect(value) | ("inspect", [value]) => inspect(value)
@ -147,7 +149,15 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
| ("reduceReverse", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) => | ("reduceReverse", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) =>
doReduceReverseArray(aValueArray, initialValue, aLambdaValue) doReduceReverseArray(aValueArray, initialValue, aLambdaValue)
| ("reverse", [EvArray(aValueArray)]) => aValueArray->Belt.Array.reverse->EvArray->Ok | ("reverse", [EvArray(aValueArray)]) => aValueArray->Belt.Array.reverse->EvArray->Ok
| call => callMathJs(call) | (_, [EvBool(_)])
| (_, [EvNumber(_)])
| (_, [EvString(_)])
| (_, [EvBool(_), EvBool(_)])
| (_, [EvNumber(_), EvNumber(_)])
| (_, [EvString(_), EvString(_)]) =>
callMathJs(call)
| call =>
Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)) // Report full type signature as error
} }
} }

View File

@ -4,6 +4,7 @@
Macros are used to define language building blocks. They are like Lisp macros. Macros are used to define language building blocks. They are like Lisp macros.
*/ */
module Bindings = Reducer_Expression_Bindings module Bindings = Reducer_Expression_Bindings
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.ExpressionValue module ExpressionValue = ReducerInterface.ExpressionValue
module ExpressionWithContext = Reducer_ExpressionWithContext module ExpressionWithContext = Reducer_ExpressionWithContext
@ -24,7 +25,7 @@ let dispatchMacroCall = (
): result<expressionWithContext, errorValue> => { ): result<expressionWithContext, errorValue> => {
let doBindStatement = (bindingExpr: expression, statement: expression, environment) => let doBindStatement = (bindingExpr: expression, statement: expression, environment) =>
switch statement { switch statement {
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => { | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$_let_$")), symbolExpr, statement}) => {
let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment) let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment)
rExternalBindingsValue->Result.flatMap(externalBindingsValue => { rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
@ -33,7 +34,7 @@ let dispatchMacroCall = (
// Js.log( // Js.log(
// `bindStatement ${Bindings.toString(newBindings)}<==${ExpressionT.toString( // `bindStatement ${Bindings.toString(newBindings)}<==${ExpressionT.toString(
// bindingExpr, // bindingExpr,
// )} statement: $let ${ExpressionT.toString(symbolExpr)}=${ExpressionT.toString( // )} statement: $_let_$ ${ExpressionT.toString(symbolExpr)}=${ExpressionT.toString(
// statement, // statement,
// )}`, // )}`,
// ) // )
@ -42,7 +43,7 @@ let dispatchMacroCall = (
rNewStatement->Result.map(newStatement => rNewStatement->Result.map(newStatement =>
ExpressionWithContext.withContext( ExpressionWithContext.withContext(
eFunction( eFunction(
"$setBindings", "$_setBindings_$",
list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement}, list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement},
), ),
newBindings, newBindings,
@ -58,7 +59,7 @@ let dispatchMacroCall = (
errorValue, errorValue,
> => > =>
switch statement { switch statement {
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => { | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$_let_$")), symbolExpr, statement}) => {
let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment) let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment)
rExternalBindingsValue->Result.flatMap(externalBindingsValue => { rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
@ -67,10 +68,10 @@ let dispatchMacroCall = (
rNewStatement->Result.map(newStatement => rNewStatement->Result.map(newStatement =>
ExpressionWithContext.withContext( ExpressionWithContext.withContext(
eFunction( eFunction(
"$exportBindings", "$_exportBindings_$",
list{ list{
eFunction( eFunction(
"$setBindings", "$_setBindings_$",
list{ list{
newBindings->Bindings.toExternalBindings->eRecord, newBindings->Bindings.toExternalBindings->eRecord,
symbolExpr, symbolExpr,
@ -139,7 +140,8 @@ let dispatchMacroCall = (
bindings: ExpressionT.bindings, bindings: ExpressionT.bindings,
environment, environment,
): result<expressionWithContext, errorValue> => { ): result<expressionWithContext, errorValue> => {
let rCondition = reduceExpression(condition, bindings, environment) let blockCondition = ExpressionBuilder.eBlock(list{condition})
let rCondition = reduceExpression(blockCondition, bindings, environment)
rCondition->Result.flatMap(conditionValue => rCondition->Result.flatMap(conditionValue =>
switch conditionValue { switch conditionValue {
| ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok | ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok
@ -155,31 +157,32 @@ let dispatchMacroCall = (
> => > =>
switch aList { switch aList {
| list{ | list{
ExpressionT.EValue(EvCall("$$bindStatement")), ExpressionT.EValue(EvCall("$$_bindStatement_$$")),
bindingExpr: ExpressionT.expression, bindingExpr: ExpressionT.expression,
statement, statement,
} => } =>
doBindStatement(bindingExpr, statement, environment) doBindStatement(bindingExpr, statement, environment)
| list{ExpressionT.EValue(EvCall("$$bindStatement")), statement} => | list{ExpressionT.EValue(EvCall("$$_bindStatement_$$")), statement} =>
// bindings of the context are used when there is no binding expression // bindings of the context are used when there is no binding expression
doBindStatement(eRecord(Bindings.toExternalBindings(bindings)), statement, environment) doBindStatement(eRecord(Bindings.toExternalBindings(bindings)), statement, environment)
| list{ | list{
ExpressionT.EValue(EvCall("$$bindExpression")), ExpressionT.EValue(EvCall("$$_bindExpression_$$")),
bindingExpr: ExpressionT.expression, bindingExpr: ExpressionT.expression,
expression, expression,
} => } =>
doBindExpression(bindingExpr, expression, environment) doBindExpression(bindingExpr, expression, environment)
| list{ExpressionT.EValue(EvCall("$$bindExpression")), expression} => | list{ExpressionT.EValue(EvCall("$$_bindExpression_$$")), expression} =>
// bindings of the context are used when there is no binding expression // bindings of the context are used when there is no binding expression
doBindExpression(eRecord(Bindings.toExternalBindings(bindings)), expression, environment) doBindExpression(eRecord(Bindings.toExternalBindings(bindings)), expression, environment)
| list{ExpressionT.EValue(EvCall("$$block")), ...exprs} => doBlock(exprs, bindings, environment) | list{ExpressionT.EValue(EvCall("$$_block_$$")), ...exprs} =>
doBlock(exprs, bindings, environment)
| list{ | list{
ExpressionT.EValue(EvCall("$$lambda")), ExpressionT.EValue(EvCall("$$_lambda_$$")),
ExpressionT.EValue(EvArrayString(parameters)), ExpressionT.EValue(EvArrayString(parameters)),
lambdaDefinition, lambdaDefinition,
} => } =>
doLambdaDefinition(bindings, parameters, lambdaDefinition) doLambdaDefinition(bindings, parameters, lambdaDefinition)
| list{ExpressionT.EValue(EvCall("$$ternary")), condition, ifTrue, ifFalse} => | list{ExpressionT.EValue(EvCall("$$_ternary_$$")), condition, ifTrue, ifFalse} =>
doTernary(condition, ifTrue, ifFalse, bindings, environment) doTernary(condition, ifTrue, ifFalse, bindings, environment)
| _ => ExpressionWithContext.noContext(ExpressionT.EList(aList))->Ok | _ => ExpressionWithContext.noContext(ExpressionT.EList(aList))->Ok
} }

View File

@ -7,6 +7,7 @@ type errorValue =
| REOperationError(Operation.operationError) | REOperationError(Operation.operationError)
| REExpressionExpected | REExpressionExpected
| REFunctionExpected(string) | REFunctionExpected(string)
| REFunctionNotFound(string)
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception | REJavaScriptExn(option<string>, option<string>) // Javascript Exception
| REMacroNotFound(string) | REMacroNotFound(string)
| RENotAFunction(string) | RENotAFunction(string)
@ -29,6 +30,7 @@ let errorToString = err =>
| REAssignmentExpected => "Assignment expected" | REAssignmentExpected => "Assignment expected"
| REExpressionExpected => "Expression expected" | REExpressionExpected => "Expression expected"
| REFunctionExpected(msg) => `Function expected: ${msg}` | REFunctionExpected(msg) => `Function expected: ${msg}`
| REFunctionNotFound(msg) => `Function not found: ${msg}`
| REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}` | REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}`
| REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}` | REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}`
| REJavaScriptExn(omsg, oname) => { | REJavaScriptExn(omsg, oname) => {

View File

@ -18,13 +18,10 @@ type internalCode = ReducerInterface_ExpressionValue.internalCode
type t = expression type t = expression
/* /*
Converts a MathJs code to expression Converts a Squigle code to expression
*/ */
let parse_ = (expr: string, parser, converter): result<t, errorValue> => let parse = (peggyCode: string): result<t, errorValue> =>
expr->parser->Result.flatMap(node => converter(node)) peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode)
let parse = (mathJsCode: string): result<t, errorValue> =>
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
/* /*
Recursively evaluate/reduce the expression (Lisp AST) Recursively evaluate/reduce the expression (Lisp AST)
@ -77,11 +74,30 @@ and reduceValueList = (valueList: list<expressionValue>, environment): result<
'e, 'e,
> => > =>
switch valueList { switch valueList {
| list{EvCall(fName), ...args} => | list{EvCall(fName), ...args} => {
(fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment, reduceExpression) let rCheckedArgs = switch fName == "$_setBindings_$" {
| false => args->Lambda.checkIfReduced
| true => args->Ok
}
rCheckedArgs->Result.flatMap(checkedArgs =>
(fName, checkedArgs->Belt.List.toArray)->BuiltIn.dispatch(environment, reduceExpression)
)
}
| list{EvLambda(_)} =>
// TODO: remove on solving issue#558
valueList
->Lambda.checkIfReduced
->Result.flatMap(reducedValueList =>
reducedValueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
)
| list{EvLambda(lamdaCall), ...args} => | list{EvLambda(lamdaCall), ...args} =>
Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression) args
->Lambda.checkIfReduced
->Result.flatMap(checkedArgs =>
Lambda.doLambdaCall(lamdaCall, checkedArgs, environment, reduceExpression)
)
| _ => | _ =>
valueList valueList
->Lambda.checkIfReduced ->Lambda.checkIfReduced
@ -116,7 +132,7 @@ let evaluateUsingOptions = (
} }
/* /*
Evaluates MathJs code and bindings via Reducer and answers the result Evaluates Squiggle code and bindings via Reducer and answers the result
*/ */
let evaluate = (code: string): result<expressionValue, errorValue> => { let evaluate = (code: string): result<expressionValue, errorValue> => {
evaluateUsingOptions(~environment=None, ~externalBindings=None, code) evaluateUsingOptions(~environment=None, ~externalBindings=None, code)

View File

@ -48,19 +48,19 @@ let eSymbol = (name: string): expression => name->BExpressionValue.EvSymbol->BEx
let eList = (list: list<expression>): expression => list->BExpressionT.EList let eList = (list: list<expression>): expression => list->BExpressionT.EList
let eBlock = (exprs: list<expression>): expression => eFunction("$$block", exprs) let eBlock = (exprs: list<expression>): expression => eFunction("$$_block_$$", exprs)
let eLetStatement = (symbol: string, valueExpression: expression): expression => let eLetStatement = (symbol: string, valueExpression: expression): expression =>
eFunction("$let", list{eSymbol(symbol), valueExpression}) eFunction("$_let_$", list{eSymbol(symbol), valueExpression})
let eBindStatement = (bindingExpr: expression, letStatement: expression): expression => let eBindStatement = (bindingExpr: expression, letStatement: expression): expression =>
eFunction("$$bindStatement", list{bindingExpr, letStatement}) eFunction("$$_bindStatement_$$", list{bindingExpr, letStatement})
let eBindStatementDefault = (letStatement: expression): expression => let eBindStatementDefault = (letStatement: expression): expression =>
eFunction("$$bindStatement", list{letStatement}) eFunction("$$_bindStatement_$$", list{letStatement})
let eBindExpression = (bindingExpr: expression, expression: expression): expression => let eBindExpression = (bindingExpr: expression, expression: expression): expression =>
eFunction("$$bindExpression", list{bindingExpr, expression}) eFunction("$$_bindExpression_$$", list{bindingExpr, expression})
let eBindExpressionDefault = (expression: expression): expression => let eBindExpressionDefault = (expression: expression): expression =>
eFunction("$$bindExpression", list{expression}) eFunction("$$_bindExpression_$$", list{expression})

View File

@ -42,6 +42,12 @@ let toStringResult = codeResult =>
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
} }
let toStringResultOkless = codeResult =>
switch codeResult {
| Ok(a) => toString(a)
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
}
let inspect = (expr: expression): expression => { let inspect = (expr: expression): expression => {
Js.log(toString(expr)) Js.log(toString(expr))
expr expr

View File

@ -1,3 +1 @@
module Eval = Reducer_MathJs_Eval module Eval = Reducer_MathJs_Eval
module Parse = Reducer_MathJs_Parse
module ToExpression = Reducer_MathJs_ToExpression

View File

@ -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

View File

@ -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}))

View File

@ -0,0 +1,346 @@
// 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}
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda
outerBlock
= statements:array_statements finalExpression: (statementSeparator @expression)?
{ if (finalExpression != null) { statements.push(finalExpression) }
return nodeBlock(statements) }
/ finalExpression: expression
{ return nodeBlock([finalExpression])}
innerBlockOrExpression
= quotedInnerBlock
/ finalExpression: expression
{ return nodeBlock([finalExpression])}
quotedInnerBlock
= '{' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}'
{ statements.push(finalExpression)
return nodeBlock(statements) }
/ '{' _nl finalExpression: expression _nl '}'
{ return nodeBlock([finalExpression]) }
array_statements
= head:statement tail:(statementSeparator @array_statements )
{ return [head, ...tail] }
/ head:statement
{ return [head] }
statement
= letStatement
/ defunStatement
letStatement
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
{ return nodeLetStatment(variable, value) }
defunStatement
= variable:identifier '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
{ var value = nodeLambda(args, body)
return nodeLetStatment(variable, value) }
assignmentOp "assignment" = '='
array_parameters
= head:dollarIdentifier tail:(_ ',' _nl @dollarIdentifier)*
{ return [head, ...tail]; }
expression = ifthenelse / ternary / logicalAdditive
ifthenelse
= 'if' __nl condition:logicalAdditive
__nl 'then' __nl trueExpression:innerBlockOrExpression
__nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression)
{ 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:logicalAdditiveOp _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
logicalAdditiveOp "operator" = '||'
// start binary operators
logicalMultiplicative
= head:equality tail:(_ operator:logicalMultiplicativeOp _nl arg:equality {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
logicalMultiplicativeOp "operator" = '&&'
equality
= left:relational _ operator:equalityOp _nl right:relational
{ return makeFunctionCall(toFunction[operator], [left, right])}
/ relational
equalityOp "operator" = '=='/'!='
relational
= left:additive _ operator:relationalOp _nl right:additive
{ return makeFunctionCall(toFunction[operator], [left, right])}
/ additive
relationalOp "operator" = '<='/'<'/'>='/'>'
additive
= head:multiplicative tail:(_ operator:additiveOp _nl arg:multiplicative {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
additiveOp "operator" = '+' / '-' / '.+' / '.-'
multiplicative
= head:power tail:(_ operator:multiplicativeOp _nl arg:power {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
multiplicativeOp "operator" = '*' / '/' / '.*' / './'
power
= head:credibleInterval tail:(_ operator:powerOp _nl arg:credibleInterval {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
powerOp "operator" = '^' / '.^'
credibleInterval
= head:chainFunctionCall tail:(__ operator:credibleIntervalOp __nl arg:chainFunctionCall {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(toFunction[element.operator], [result, element.right])
}, head)}
credibleIntervalOp "operator" = 'to'
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:dollarIdentifier '(' _nl args:array_functionArguments _nl ')'
{ return {fnName: fn.value, args: args}}
/ fn:dollarIdentifier '(' _nl ')'
{ return {fnName: fn.value, args: []}}
/ fn:dollarIdentifier
{ return {fnName: fn.value, args: []}}
// end of binary operators
unary
= unaryOperator:unaryOperator _nl right:(unary/postOperator)
{ return makeFunctionCall(unaryToFunction[unaryOperator], [right])}
/ postOperator
unaryOperator "unary operator"
= ('-' / '.-' / '!' )
postOperator = indexedValue
indexedValue
= collectionElement
/ recordElement
/ atom
collectionElement
= head:atom &('['/'('/'.')
tail:(
_ '[' _nl arg:expression _nl ']' {return {fn: postOperatorToFunction['[]'], args: [arg]}}
/ _ '(' _nl args:array_functionArguments _nl ')' {return {fn: postOperatorToFunction['()'], args: args}}
/ '.' arg:$dollarIdentifier {return {fn: postOperatorToFunction['[]'], args: [nodeString(arg)]}}
)*
{ return tail.reduce(function(result, element) {
return makeFunctionCall(element.fn, [result, ...element.args])
}, head)}
array_functionArguments
= head:expression tail:(_ ',' _nl @expression)*
{ return [head, ...tail]; }
recordElement
= head:dollarIdentifier &'.'
tail:(_ '.' _nl arg:$dollarIdentifier {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
/ dollarIdentifier
identifier 'identifier'
= ([_a-z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
dollarIdentifier '$identifier'
= ([\$_a-z]+[\$_a-z0-9]i*) {return nodeIdentifier(text())}
string 'string'
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
integer 'integer'
= d+ !"\." ![e]i
{ return nodeInteger(parseInt(text()))}
float 'float'
= $(((d+ "\." d*) / ("\." d+)) floatExponent? / d+ floatExponent)
{ return nodeFloat(parseFloat(text()))}
floatExponent = [e]i '-'? d+
d = [0-9]
boolean 'boolean'
= ('true'/'false')
{ return nodeBoolean(text() === 'true')}
valueConstructor
= recordConstructor
/ arrayConstructor
/ lambda
/ quotedInnerBlock
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)}
_ 'whitespace'
= whiteSpaceCharactersOrComment*
_nl 'optional whitespace or newline'
= (whiteSpaceCharactersOrComment / commentOrNewLine)*
__ 'whitespace'
= whiteSpaceCharactersOrComment+
__nl 'whitespace or newline'
= (whiteSpaceCharactersOrComment / commentOrNewLine )+
statementSeparator 'statement separator'
= _ (';'/ commentOrNewLine)+ _nl
commentOrNewLine = finalComment? newLine
finalComment "line comment"
= _ ('//'/'#') @([^\r\n]*)
whiteSpaceCharactersOrComment = whiteSpaceCharacters / delimitedComment
delimitedComment "comment"
= '/*' @([^*]*) '*/'
whiteSpaceCharacters = [ \t]
newLine "newline"
= [\n\r]

View File

@ -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)})`
}

View File

@ -0,0 +1,48 @@
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"]),
},
)
}
}

View File

@ -87,6 +87,12 @@ let toStringResult = x =>
| Error(m) => `Error(${ErrorValue.errorToString(m)})` | Error(m) => `Error(${ErrorValue.errorToString(m)})`
} }
let toStringResultOkless = (codeResult: result<expressionValue, ErrorValue.errorValue>): string =>
switch codeResult {
| Ok(a) => toString(a)
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
}
let toStringResultRecord = x => let toStringResultRecord = x =>
switch x { switch x {
| Ok(a) => `Ok(${toStringRecord(a)})` | Ok(a) => `Ok(${toStringRecord(a)})`
@ -98,3 +104,57 @@ type environment = DistributionOperation.env
@genType @genType
let defaultEnvironment: environment = DistributionOperation.defaultEnv let defaultEnvironment: environment = DistributionOperation.defaultEnv
type expressionValueType =
| EvtArray
| EvtArrayString
| EvtBool
| EvtCall
| EvtDistribution
| EvtLambda
| EvtNumber
| EvtRecord
| EvtString
| EvtSymbol
type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature =
FunctionDefinitionSignature(functionCallSignature, expressionValueType)
let valueToValueType = value =>
switch value {
| EvArray(_) => EvtArray
| EvArrayString(_) => EvtArray
| EvBool(_) => EvtBool
| EvCall(_) => EvtCall
| EvDistribution(_) => EvtDistribution
| EvLambda(_) => EvtLambda
| EvNumber(_) => EvtNumber
| EvRecord(_) => EvtRecord
| EvString(_) => EvtArray
| EvSymbol(_) => EvtSymbol
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
let (fn, args) = functionCall
CallSignature(fn, args->Js.Array2.map(valueToValueType))
}
let valueTypeToString = (valueType: expressionValueType): string =>
switch valueType {
| EvtArray => `Array`
| EvtArrayString => `ArrayString`
| EvtBool => `Bool`
| EvtCall => `Call`
| EvtDistribution => `Distribution`
| EvtLambda => `Lambda`
| EvtNumber => `Number`
| EvtRecord => `Record`
| EvtString => `String`
| EvtSymbol => `Symbol`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
let CallSignature(fn, args) = functionCallSignature
`${fn}(${args->Js.Array2.map(valueTypeToString)->Js.Array2.toString})`
}

1663
yarn.lock

File diff suppressed because it is too large Load Diff