Merged with develop
This commit is contained in:
commit
faf4718f4d
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
|
@ -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: "⬆️"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
1
packages/squiggle-lang/.gitignore
vendored
1
packages/squiggle-lang/.gitignore
vendored
|
@ -21,3 +21,4 @@ dist
|
||||||
_coverage
|
_coverage
|
||||||
coverage
|
coverage
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||||
|
|
|
@ -4,4 +4,5 @@ lib
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
coverage/
|
coverage/
|
||||||
.cache/
|
.cache/
|
||||||
|
Reducer_Peggy_GeneratedParser.js
|
||||||
|
|
|
@ -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.)})
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
module Parse = Reducer_MathJs.Parse
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let expectParseToBe = (expr, answer) =>
|
|
||||||
Parse.parse(expr)->Result.flatMap(Parse.castNodeType)->Parse.toStringResult->expect->toBe(answer)
|
|
||||||
|
|
||||||
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
let testDescriptionParse = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
module MySkip = {
|
|
||||||
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Skip.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
module MyOnly = {
|
|
||||||
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Only.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("MathJs parse", () => {
|
|
||||||
describe("literals operators parenthesis", () => {
|
|
||||||
testParse("1", "1")
|
|
||||||
testParse("'hello'", "'hello'")
|
|
||||||
testParse("true", "true")
|
|
||||||
testParse("1+2", "add(1, 2)")
|
|
||||||
testParse("add(1,2)", "add(1, 2)")
|
|
||||||
testParse("(1)", "(1)")
|
|
||||||
testParse("(1+2)", "(add(1, 2))")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("multi-line", () => {
|
|
||||||
testParse("1; 2", "{1; 2}")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("variables", () => {
|
|
||||||
testParse("x = 1", "x = 1")
|
|
||||||
testParse("x", "x")
|
|
||||||
testParse("x = 1; x", "{x = 1; x}")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("functions", () => {
|
|
||||||
testParse("identity(x) = x", "identity = (x) => x")
|
|
||||||
testParse("identity(x)", "identity(x)")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("arrays", () => {
|
|
||||||
testDescriptionParse("empty", "[]", "[]")
|
|
||||||
testDescriptionParse("define", "[0, 1, 2]", "[0, 1, 2]")
|
|
||||||
testDescriptionParse("define with strings", "['hello', 'world']", "['hello', 'world']")
|
|
||||||
testParse("range(0, 4)", "range(0, 4)")
|
|
||||||
testDescriptionParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("records", () => {
|
|
||||||
testDescriptionParse("define", "{a: 1, b: 2}", "{a: 1, b: 2}")
|
|
||||||
testDescriptionParse("use", "record.property", "record['property']")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("comments", () => {
|
|
||||||
testDescriptionParse("define", "1 # This is a comment", "1")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("ternary operator", () => {
|
|
||||||
testParse("1 ? 2 : 3", "ternary(1, 2, 3)")
|
|
||||||
testParse("1 ? 2 : 3 ? 4 : 5", "ternary(1, 2, ternary(3, 4, 5))")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,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)}",
|
||||||
|
)
|
||||||
|
})
|
|
@ -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)}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -19,6 +19,9 @@ let expectParseToBe = (expr: string, answer: string) =>
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
let expectEvalToBe = (expr: string, answer: string) =>
|
||||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
let expectEvalError = (expr: string) =>
|
||||||
|
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")
|
||||||
|
|
||||||
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
||||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
||||||
->ExpressionValue.toStringResult
|
->ExpressionValue.toStringResult
|
||||||
|
@ -29,6 +32,7 @@ let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, ans
|
||||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||||
test(desc, () => expectParseToBe(expr, answer))
|
test(desc, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
|
let testEvalError = expr => test(expr, () => expectEvalError(expr))
|
||||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||||
|
|
|
@ -2,62 +2,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})",
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)")
|
||||||
})
|
})
|
||||||
|
|
|
@ -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", () => {
|
||||||
|
|
|
@ -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)",
|
||||||
)
|
// )
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)))")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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))
|
||||||
|
],
|
||||||
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -42,6 +42,12 @@ let toStringResult = codeResult =>
|
||||||
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toStringResultOkless = codeResult =>
|
||||||
|
switch codeResult {
|
||||||
|
| Ok(a) => toString(a)
|
||||||
|
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
|
||||||
|
}
|
||||||
|
|
||||||
let inspect = (expr: expression): expression => {
|
let inspect = (expr: expression): expression => {
|
||||||
Js.log(toString(expr))
|
Js.log(toString(expr))
|
||||||
expr
|
expr
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
module Eval = Reducer_MathJs_Eval
|
module Eval = Reducer_MathJs_Eval
|
||||||
module Parse = Reducer_MathJs_Parse
|
|
||||||
module ToExpression = Reducer_MathJs_ToExpression
|
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
/*
|
|
||||||
MathJs Nodes
|
|
||||||
We make MathJs Nodes strong-typed
|
|
||||||
*/
|
|
||||||
module Extra = Reducer_Extra
|
|
||||||
open Reducer_ErrorValue
|
|
||||||
|
|
||||||
type node = {"type": string, "isNode": bool, "comment": string}
|
|
||||||
type arrayNode = {...node, "items": array<node>}
|
|
||||||
type block = {"node": node}
|
|
||||||
type blockNode = {...node, "blocks": array<block>}
|
|
||||||
type conditionalNode = {...node, "condition": node, "trueExpr": node, "falseExpr": node}
|
|
||||||
type constantNode = {...node, "value": unit}
|
|
||||||
type functionAssignmentNode = {...node, "name": string, "params": array<string>, "expr": node}
|
|
||||||
type indexNode = {...node, "dimensions": array<node>}
|
|
||||||
type objectNode = {...node, "properties": Js.Dict.t<node>}
|
|
||||||
type accessorNode = {...node, "object": node, "index": indexNode, "name": string}
|
|
||||||
type parenthesisNode = {...node, "content": node}
|
|
||||||
//rangeNode
|
|
||||||
//relationalNode
|
|
||||||
type symbolNode = {...node, "name": string}
|
|
||||||
type functionNode = {...node, "fn": unit, "args": array<node>}
|
|
||||||
type operatorNode = {...functionNode, "op": string}
|
|
||||||
type assignmentNode = {...node, "object": symbolNode, "value": node}
|
|
||||||
type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node}
|
|
||||||
type assignmentNodeWIndex = {...assignmentNodeWAccessor, "index": Js.null<indexNode>}
|
|
||||||
|
|
||||||
external castAccessorNode: node => accessorNode = "%identity"
|
|
||||||
external castArrayNode: node => arrayNode = "%identity"
|
|
||||||
external castAssignmentNode: node => assignmentNode = "%identity"
|
|
||||||
external castAssignmentNodeWAccessor: node => assignmentNodeWAccessor = "%identity"
|
|
||||||
external castAssignmentNodeWIndex: node => assignmentNodeWIndex = "%identity"
|
|
||||||
external castBlockNode: node => blockNode = "%identity"
|
|
||||||
external castConditionalNode: node => conditionalNode = "%identity"
|
|
||||||
external castConstantNode: node => constantNode = "%identity"
|
|
||||||
external castFunctionAssignmentNode: node => functionAssignmentNode = "%identity"
|
|
||||||
external castFunctionNode: node => functionNode = "%identity"
|
|
||||||
external castIndexNode: node => indexNode = "%identity"
|
|
||||||
external castObjectNode: node => objectNode = "%identity"
|
|
||||||
external castOperatorNode: node => operatorNode = "%identity"
|
|
||||||
external castOperatorNodeToFunctionNode: operatorNode => functionNode = "%identity"
|
|
||||||
external castParenthesisNode: node => parenthesisNode = "%identity"
|
|
||||||
external castSymbolNode: node => symbolNode = "%identity"
|
|
||||||
|
|
||||||
/*
|
|
||||||
MathJs Parser
|
|
||||||
*/
|
|
||||||
@module("mathjs") external parse__: string => node = "parse"
|
|
||||||
|
|
||||||
let parse = (expr: string): result<node, errorValue> =>
|
|
||||||
try {
|
|
||||||
Ok(parse__(expr))
|
|
||||||
} catch {
|
|
||||||
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type mathJsNode =
|
|
||||||
| MjAccessorNode(accessorNode)
|
|
||||||
| MjArrayNode(arrayNode)
|
|
||||||
| MjAssignmentNode(assignmentNode)
|
|
||||||
| MjBlockNode(blockNode)
|
|
||||||
| MjConditionalNode(conditionalNode)
|
|
||||||
| MjConstantNode(constantNode)
|
|
||||||
| MjFunctionAssignmentNode(functionAssignmentNode)
|
|
||||||
| MjFunctionNode(functionNode)
|
|
||||||
| MjIndexNode(indexNode)
|
|
||||||
| MjObjectNode(objectNode)
|
|
||||||
| MjOperatorNode(operatorNode)
|
|
||||||
| MjParenthesisNode(parenthesisNode)
|
|
||||||
| MjSymbolNode(symbolNode)
|
|
||||||
|
|
||||||
let castNodeType = (node: node) => {
|
|
||||||
let decideAssignmentNode = node => {
|
|
||||||
let iNode = node->castAssignmentNodeWIndex
|
|
||||||
if Js.null == iNode["index"] && iNode["object"]["type"] == "SymbolNode" {
|
|
||||||
node->castAssignmentNode->MjAssignmentNode->Ok
|
|
||||||
} else {
|
|
||||||
RESyntaxError("Assignment to index or property not supported")->Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node["type"] {
|
|
||||||
| "AccessorNode" => node->castAccessorNode->MjAccessorNode->Ok
|
|
||||||
| "ArrayNode" => node->castArrayNode->MjArrayNode->Ok
|
|
||||||
| "AssignmentNode" => node->decideAssignmentNode
|
|
||||||
| "BlockNode" => node->castBlockNode->MjBlockNode->Ok
|
|
||||||
| "ConditionalNode" => node->castConditionalNode->MjConditionalNode->Ok
|
|
||||||
| "ConstantNode" => node->castConstantNode->MjConstantNode->Ok
|
|
||||||
| "FunctionAssignmentNode" => node->castFunctionAssignmentNode->MjFunctionAssignmentNode->Ok
|
|
||||||
| "FunctionNode" => node->castFunctionNode->MjFunctionNode->Ok
|
|
||||||
| "IndexNode" => node->castIndexNode->MjIndexNode->Ok
|
|
||||||
| "ObjectNode" => node->castObjectNode->MjObjectNode->Ok
|
|
||||||
| "OperatorNode" => node->castOperatorNode->MjOperatorNode->Ok
|
|
||||||
| "ParenthesisNode" => node->castParenthesisNode->MjParenthesisNode->Ok
|
|
||||||
| "SymbolNode" => node->castSymbolNode->MjSymbolNode->Ok
|
|
||||||
| _ => RETodo(`Argg, unhandled MathJsNode: ${node["type"]}`)->Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
external unitAsSymbolNode: unit => symbolNode = "%identity"
|
|
||||||
external unitAsString: unit => string = "%identity"
|
|
||||||
|
|
||||||
let nameOfFunctionNode = (fNode: functionNode): string => {
|
|
||||||
let name = fNode["fn"]
|
|
||||||
if Js.typeof(name) == "string" {
|
|
||||||
name->unitAsString
|
|
||||||
} else {
|
|
||||||
(name->unitAsSymbolNode)["name"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let rec toString = (mathJsNode: mathJsNode): string => {
|
|
||||||
let toStringValue = (a: 'a): string =>
|
|
||||||
if Js.typeof(a) == "string" {
|
|
||||||
`'${Js.String.make(a)}'`
|
|
||||||
} else {
|
|
||||||
Js.String.make(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
let toStringNodeArray = (nodeArray: array<node>): string =>
|
|
||||||
nodeArray
|
|
||||||
->Belt.Array.map(a => toStringMathJsNode(a))
|
|
||||||
->Extra.Array.interperse(", ")
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
let toStringFunctionAssignmentNode = (faNode: functionAssignmentNode): string => {
|
|
||||||
let paramNames = Js.Array2.toString(faNode["params"])
|
|
||||||
`${faNode["name"]} = (${paramNames}) => ${toStringMathJsNode(faNode["expr"])}`
|
|
||||||
}
|
|
||||||
let toStringFunctionNode = (fnode: functionNode): string =>
|
|
||||||
`${fnode->nameOfFunctionNode}(${fnode["args"]->toStringNodeArray})`
|
|
||||||
|
|
||||||
let toStringObjectEntry = ((key: string, value: node)): string =>
|
|
||||||
`${key}: ${value->toStringMathJsNode}`
|
|
||||||
|
|
||||||
let toStringObjectNode = (oNode: objectNode): string =>
|
|
||||||
`{${oNode["properties"]
|
|
||||||
->Js.Dict.entries
|
|
||||||
->Belt.Array.map(entry => entry->toStringObjectEntry)
|
|
||||||
->Extra.Array.interperse(", ")
|
|
||||||
->Js.String.concatMany("")}}`
|
|
||||||
|
|
||||||
let toStringIndexNode = (iNode: indexNode): string =>
|
|
||||||
iNode["dimensions"]
|
|
||||||
->Belt.Array.map(each => toStringResult(each->castNodeType))
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
let toStringSymbolNode = (sNode: symbolNode): string => sNode["name"]
|
|
||||||
|
|
||||||
let toStringBlocks = (blocks: array<block>): string =>
|
|
||||||
blocks
|
|
||||||
->Belt.Array.map(each => each["node"]->castNodeType->toStringResult)
|
|
||||||
->Extra.Array.interperse("; ")
|
|
||||||
->Js.String.concatMany("")
|
|
||||||
|
|
||||||
switch mathJsNode {
|
|
||||||
| MjAccessorNode(aNode) =>
|
|
||||||
`${aNode["object"]->toStringMathJsNode}[${aNode["index"]->toStringIndexNode}]`
|
|
||||||
| MjArrayNode(aNode) => `[${aNode["items"]->toStringNodeArray}]`
|
|
||||||
| MjAssignmentNode(aNode) =>
|
|
||||||
`${aNode["object"]->toStringSymbolNode} = ${aNode["value"]->toStringMathJsNode}`
|
|
||||||
| MjBlockNode(bNode) => `{${bNode["blocks"]->toStringBlocks}}`
|
|
||||||
| MjConditionalNode(cNode) =>
|
|
||||||
`ternary(${toStringMathJsNode(cNode["condition"])}, ${toStringMathJsNode(
|
|
||||||
cNode["trueExpr"],
|
|
||||||
)}, ${toStringMathJsNode(cNode["falseExpr"])})`
|
|
||||||
| MjConstantNode(cNode) => cNode["value"]->toStringValue
|
|
||||||
| MjFunctionAssignmentNode(faNode) => faNode->toStringFunctionAssignmentNode
|
|
||||||
| MjFunctionNode(fNode) => fNode->toStringFunctionNode
|
|
||||||
| MjIndexNode(iNode) => iNode->toStringIndexNode
|
|
||||||
| MjObjectNode(oNode) => oNode->toStringObjectNode
|
|
||||||
| MjOperatorNode(opNode) => opNode->castOperatorNodeToFunctionNode->toStringFunctionNode
|
|
||||||
| MjParenthesisNode(pNode) => `(${toStringMathJsNode(pNode["content"])})`
|
|
||||||
| MjSymbolNode(sNode) => sNode->toStringSymbolNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
and toStringResult = (rMathJsNode: result<mathJsNode, errorValue>): string =>
|
|
||||||
switch rMathJsNode {
|
|
||||||
| Error(e) => errorToString(e)
|
|
||||||
| Ok(mathJsNode) => toString(mathJsNode)
|
|
||||||
}
|
|
||||||
and toStringMathJsNode = node => node->castNodeType->toStringResult
|
|
|
@ -1,154 +0,0 @@
|
||||||
/* * WARNING. DO NOT EDIT, BEAUTIFY, COMMENT ON OR REFACTOR THIS CODE.
|
|
||||||
We will stop using MathJs parser and
|
|
||||||
this whole file will go to trash
|
|
||||||
**/
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
|
||||||
module JavaScript = Reducer_Js
|
|
||||||
module Parse = Reducer_MathJs_Parse
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
type errorValue = ErrorValue.errorValue
|
|
||||||
type expression = ExpressionT.expression
|
|
||||||
type expressionValue = ExpressionValue.expressionValue
|
|
||||||
|
|
||||||
let blockToNode = block => block["node"]
|
|
||||||
|
|
||||||
let rec fromInnerNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|
||||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
|
||||||
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
|
||||||
Belt.List.reduceReverse(nodeList, Ok(list{}), (racc, currNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let caseFunctionNode = fNode => {
|
|
||||||
let rLispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
|
||||||
rLispArgs->Result.map(lispArgs =>
|
|
||||||
ExpressionBuilder.eFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseObjectNode = oNode => {
|
|
||||||
let fromObjectEntries = entryList => {
|
|
||||||
let rargs = Belt.List.reduceReverse(entryList, Ok(list{}), (
|
|
||||||
racc,
|
|
||||||
(key: string, value: Parse.node),
|
|
||||||
) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(value)->Result.map(valueExpression => {
|
|
||||||
let entryCode =
|
|
||||||
list{ExpressionBuilder.eString(key), valueExpression}->ExpressionT.EList
|
|
||||||
list{entryCode, ...acc}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rargs->Result.flatMap(args =>
|
|
||||||
ExpressionBuilder.eFunction("$constructRecord", list{ExpressionT.EList(args)})->Ok
|
|
||||||
) // $constructRecord gets a single argument: List of key-value paiers
|
|
||||||
}
|
|
||||||
|
|
||||||
oNode["properties"]->Js.Dict.entries->Belt.List.fromArray->fromObjectEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseIndexNode = iNode => {
|
|
||||||
let rpropertyCodeList = Belt.List.reduceReverse(
|
|
||||||
iNode["dimensions"]->Belt.List.fromArray,
|
|
||||||
Ok(list{}),
|
|
||||||
(racc, currentPropertyMathJsNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{
|
|
||||||
propertyCode,
|
|
||||||
...acc,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
rpropertyCodeList->Result.map(propertyCodeList => ExpressionT.EList(propertyCodeList))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseAccessorNode = (objectNode, indexNode) => {
|
|
||||||
caseIndexNode(indexNode)->Result.flatMap(indexCode => {
|
|
||||||
fromInnerNode(objectNode)->Result.flatMap(objectCode =>
|
|
||||||
ExpressionBuilder.eFunction("$atIndex", list{objectCode, indexCode})->Ok
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseBlock = (nodesArray: array<Parse.node>): result<expression, errorValue> => {
|
|
||||||
let rStatements: result<list<expression>, 'a> =
|
|
||||||
nodesArray
|
|
||||||
->Belt.List.fromArray
|
|
||||||
->Belt.List.reduceReverse(Ok(list{}), (racc, currNode) =>
|
|
||||||
racc->Result.flatMap(acc =>
|
|
||||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rStatements->Result.map(statements => ExpressionBuilder.eBlock(statements))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseAssignmentNode = aNode => {
|
|
||||||
let symbolName = aNode["object"]["name"]
|
|
||||||
let rValueExpression = fromInnerNode(aNode["value"])
|
|
||||||
rValueExpression->Result.map(valueExpression =>
|
|
||||||
ExpressionBuilder.eLetStatement(symbolName, valueExpression)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseFunctionAssignmentNode = faNode => {
|
|
||||||
let symbol = faNode["name"]->ExpressionBuilder.eSymbol
|
|
||||||
let rValueExpression = fromInnerNode(faNode["expr"])
|
|
||||||
|
|
||||||
rValueExpression->Result.flatMap(valueExpression => {
|
|
||||||
let lispParams = ExpressionBuilder.eArrayString(faNode["params"])
|
|
||||||
let valueBlock = ExpressionBuilder.eBlock(list{valueExpression})
|
|
||||||
let lambda = ExpressionBuilder.eFunction("$$lambda", list{lispParams, valueBlock})
|
|
||||||
ExpressionBuilder.eFunction("$let", list{symbol, lambda})->Ok
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseArrayNode = aNode => {
|
|
||||||
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
let caseConditionalNode = cndNode => {
|
|
||||||
let rCondition = fromInnerNode(cndNode["condition"])
|
|
||||||
let rTrueExpr = fromInnerNode(cndNode["trueExpr"])
|
|
||||||
let rFalse = fromInnerNode(cndNode["falseExpr"])
|
|
||||||
|
|
||||||
rCondition->Result.flatMap(condition =>
|
|
||||||
rTrueExpr->Result.flatMap(trueExpr =>
|
|
||||||
rFalse->Result.flatMap(falseExpr =>
|
|
||||||
ExpressionBuilder.eFunction("$$ternary", list{condition, trueExpr, falseExpr})->Ok
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
|
||||||
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
|
||||||
| MjArrayNode(aNode) => caseArrayNode(aNode)
|
|
||||||
| MjAssignmentNode(aNode) => caseAssignmentNode(aNode)
|
|
||||||
| MjSymbolNode(sNode) => {
|
|
||||||
let expr: expression = ExpressionBuilder.eSymbol(sNode["name"])
|
|
||||||
let rExpr: result<expression, errorValue> = expr->Ok
|
|
||||||
rExpr
|
|
||||||
}
|
|
||||||
| MjBlockNode(bNode) => bNode["blocks"]->Js.Array2.map(blockToNode)->caseBlock
|
|
||||||
| MjConditionalNode(cndNode) => caseConditionalNode(cndNode)
|
|
||||||
| MjConstantNode(cNode) =>
|
|
||||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
|
||||||
| MjFunctionAssignmentNode(faNode) => caseFunctionAssignmentNode(faNode)
|
|
||||||
| MjFunctionNode(fNode) => fNode->caseFunctionNode
|
|
||||||
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
|
||||||
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
|
||||||
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->caseFunctionNode
|
|
||||||
| MjParenthesisNode(pNode) => pNode["content"]->fromInnerNode
|
|
||||||
}
|
|
||||||
rFinalExpression
|
|
||||||
})
|
|
||||||
|
|
||||||
let fromNode = (node: Parse.node): result<expression, errorValue> =>
|
|
||||||
fromInnerNode(node)->Result.map(expr => ExpressionBuilder.eBlock(list{expr}))
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
type node = {"type": string}
|
||||||
|
|
||||||
|
@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse"
|
||||||
|
|
||||||
|
let parse = (expr: string): result<node, errorValue> =>
|
||||||
|
try {
|
||||||
|
Ok(parse__(expr))
|
||||||
|
} catch {
|
||||||
|
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeBlock = {...node, "statements": array<node>}
|
||||||
|
type nodeBoolean = {...node, "value": bool}
|
||||||
|
type nodeCallIdentifier = {...node, "value": string}
|
||||||
|
type nodeExpression = {...node, "nodes": array<node>}
|
||||||
|
type nodeFloat = {...node, "value": float}
|
||||||
|
type nodeIdentifier = {...node, "value": string}
|
||||||
|
type nodeInteger = {...node, "value": int}
|
||||||
|
type nodeKeyValue = {...node, "key": node, "value": node}
|
||||||
|
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
|
||||||
|
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
|
||||||
|
type nodeString = {...node, "value": string}
|
||||||
|
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
||||||
|
|
||||||
|
type peggyNode =
|
||||||
|
| PgNodeBlock(nodeBlock)
|
||||||
|
| PgNodeBoolean(nodeBoolean)
|
||||||
|
| PgNodeCallIdentifier(nodeCallIdentifier)
|
||||||
|
| PgNodeExpression(nodeExpression)
|
||||||
|
| PgNodeFloat(nodeFloat)
|
||||||
|
| PgNodeIdentifier(nodeIdentifier)
|
||||||
|
| PgNodeInteger(nodeInteger)
|
||||||
|
| PgNodeKeyValue(nodeKeyValue)
|
||||||
|
| PgNodeLambda(nodeLambda)
|
||||||
|
| PgNodeLetStatement(nodeLetStatement)
|
||||||
|
| PgNodeString(nodeString)
|
||||||
|
| PgNodeTernary(nodeTernary)
|
||||||
|
|
||||||
|
external castNodeBlock: node => nodeBlock = "%identity"
|
||||||
|
external castNodeBoolean: node => nodeBoolean = "%identity"
|
||||||
|
external castNodeCallIdentifier: node => nodeCallIdentifier = "%identity"
|
||||||
|
external castNodeExpression: node => nodeExpression = "%identity"
|
||||||
|
external castNodeFloat: node => nodeFloat = "%identity"
|
||||||
|
external castNodeIdentifier: node => nodeIdentifier = "%identity"
|
||||||
|
external castNodeInteger: node => nodeInteger = "%identity"
|
||||||
|
external castNodeKeyValue: node => nodeKeyValue = "%identity"
|
||||||
|
external castNodeLambda: node => nodeLambda = "%identity"
|
||||||
|
external castNodeLetStatement: node => nodeLetStatement = "%identity"
|
||||||
|
external castNodeString: node => nodeString = "%identity"
|
||||||
|
external castNodeTernary: node => nodeTernary = "%identity"
|
||||||
|
|
||||||
|
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
||||||
|
let castNodeType = (node: node) =>
|
||||||
|
switch node["type"] {
|
||||||
|
| "Block" => node->castNodeBlock->PgNodeBlock
|
||||||
|
| "Boolean" => node->castNodeBoolean->PgNodeBoolean
|
||||||
|
| "CallIdentifier" => node->castNodeCallIdentifier->PgNodeCallIdentifier
|
||||||
|
| "Expression" => node->castNodeExpression->PgNodeExpression
|
||||||
|
| "Float" => node->castNodeFloat->PgNodeFloat
|
||||||
|
| "Identifier" => node->castNodeIdentifier->PgNodeIdentifier
|
||||||
|
| "Integer" => node->castNodeInteger->PgNodeInteger
|
||||||
|
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue
|
||||||
|
| "Lambda" => node->castNodeLambda->PgNodeLambda
|
||||||
|
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement
|
||||||
|
| "String" => node->castNodeString->PgNodeString
|
||||||
|
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
||||||
|
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec pgToString = (peggyNode: peggyNode): string => {
|
||||||
|
let argsToString = (args: array<nodeIdentifier>): string =>
|
||||||
|
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
|
||||||
|
|
||||||
|
let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string =>
|
||||||
|
nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("")
|
||||||
|
|
||||||
|
switch peggyNode {
|
||||||
|
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"
|
||||||
|
| PgNodeBoolean(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeCallIdentifier(node) => `::${Js.String.make(node["value"])}` // This is an identifier also but for function names
|
||||||
|
| PgNodeExpression(node) => "(" ++ node["nodes"]->nodesToStringUsingSeparator(" ") ++ ")"
|
||||||
|
| PgNodeFloat(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeIdentifier(node) => `:${node["value"]}`
|
||||||
|
| PgNodeInteger(node) => node["value"]->Js.String.make
|
||||||
|
| PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"])
|
||||||
|
| PgNodeLambda(node) =>
|
||||||
|
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
|
||||||
|
| PgNodeLetStatement(node) =>
|
||||||
|
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
|
||||||
|
| PgNodeString(node) => `'${node["value"]->Js.String.make}'`
|
||||||
|
| PgNodeTernary(node) =>
|
||||||
|
"(::$$_ternary_$$ " ++
|
||||||
|
toString(node["condition"]) ++
|
||||||
|
" " ++
|
||||||
|
toString(node["trueExpression"]) ++
|
||||||
|
" " ++
|
||||||
|
toString(node["falseExpression"]) ++ ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
and toString = (node: node): string => node->castNodeType->pgToString
|
||||||
|
|
||||||
|
let toStringResult = (rNode: result<node, errorValue>): string =>
|
||||||
|
switch rNode {
|
||||||
|
| Ok(node) => toString(node)
|
||||||
|
| Error(error) => `Error(${errorToString(error)})`
|
||||||
|
}
|
|
@ -0,0 +1,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"]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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})`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user