diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 898b2e95..9e65c130 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -164,6 +164,18 @@ const SquiggleItem: React.FC = ({ ))} ); + case "arraystring": + return ( + + {expression.value.map((r) => `"${r}"`)} + + ); + case "lambda": + return ( + + There is no viewer currently available for function types. + + ); } }; diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros_test.res new file mode 100644 index 00000000..7bbc43dd --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros_test.res @@ -0,0 +1,142 @@ +open Jest +// open Expect + +open Reducer_Expression_ExpressionBuilder +open Reducer_TestMacroHelpers +module ExpressionT = Reducer_Expression_T + +let exampleExpression = eNumber(1.) +let exampleExpressionY = eSymbol("y") +let exampleStatementY = eLetStatement("y", eNumber(1.)) +let exampleStatementX = eLetStatement("y", eSymbol("x")) +let exampleStatementZ = eLetStatement("z", eSymbol("y")) + +// If it is not a macro then it is not expanded +testMacro([], exampleExpression, "Ok(1)") + +describe("bindStatement", () => { + // A statement is bound by the bindings created by the previous statement + testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1))") + // Then it answers the bindings for the next statement when reduced + testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})") + // Now let's feed a binding to see what happens + testMacro( + [], + eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX), + "Ok((:$setBindings {x: 2} :y 2))", + ) + // An expression does not return a binding, thus error + testMacro([], eBindStatement(eBindings([]), exampleExpression), "Error(Assignment expected)") + // When bindings from previous statement are missing the context is injected. This must be the first statement of a block + testMacro( + [("z", EvNumber(99.))], + eBindStatementDefault(exampleStatementY), + "Ok((:$setBindings {z: 99} :y 1))", + ) +}) + +describe("bindExpression", () => { + // x is simply bound in the expression + testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2)") + // When an let statement is the end expression then bindings are returned + testMacro( + [], + eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY), + "Ok((:$exportBindings (:$setBindings {x: 2} :y 1)))", + ) + // Now let's reduce that expression + testMacroEval( + [], + eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY), + "Ok({x: 2,y: 1})", + ) + // When bindings are missing the context is injected. This must be the first and last statement of a block + testMacroEval( + [("z", EvNumber(99.))], + eBindExpressionDefault(exampleStatementY), + "Ok({y: 1,z: 99})", + ) +}) + +describe("block", () => { + // Block with a single expression + testMacro([], eBlock(list{exampleExpression}), "Ok((:$$bindExpression 1))") + testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)") + // Block with a single statement + testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$bindExpression (:$let :y 1)))") + testMacroEval([], eBlock(list{exampleStatementY}), "Ok({y: 1})") + // Block with a statement and an expression + testMacro( + [], + eBlock(list{exampleStatementY, exampleExpressionY}), + "Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) :y))", + ) + testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)") + // Block with a statement and another statement + testMacro( + [], + eBlock(list{exampleStatementY, exampleStatementZ}), + "Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) (:$let :z :y)))", + ) + testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})") + // Block inside a block + testMacro( + [], + eBlock(list{eBlock(list{exampleExpression})}), + "Ok((:$$bindExpression (:$$block 1)))", + ) + testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)") + // Block assigned to a variable + testMacro( + [], + eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}), + "Ok((:$$bindExpression (:$let :z (:$$block (:$$block :y)))))", + ) + testMacroEval( + [], + eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}), + "Ok({z: :y})", + ) + // Empty block + testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error + // :$$block (:$$block (:$let :y (:add :x 1)) :y)" + testMacro( + [], + eBlock(list{ + eBlock(list{ + eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})), + eSymbol("y"), + }), + }), + "Ok((:$$bindExpression (:$$block (:$let :y (:add :x 1)) :y)))", + ) + MyOnly.testMacroEval( + [("x", EvNumber(1.))], + eBlock(list{ + eBlock(list{ + eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})), + eSymbol("y"), + }), + }), + "Ok(2)", + ) +}) + +describe("lambda", () => { + // assign a lambda to a variable + let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY}) + testMacro([], lambdaExpression, "Ok(lambda(y=>internal))") + // call a lambda + let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList + testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))") + testMacroEval([], callLambdaExpression, "Ok(1)") + // Parameters shadow the outer scope + testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(1)") + // When not shadowed by the parameters, the outer scope variables are available + let lambdaExpression = eFunction( + "$$lambda", + list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})}, + ) + let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)}) + testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(667)") +}) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Expression/Reducer_Expression_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Expression/Reducer_Expression_test.res new file mode 100644 index 00000000..f20d95f7 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Expression/Reducer_Expression_test.res @@ -0,0 +1,6 @@ +open Jest +open Expect + +test("dummy", () => { + expect(true)->toBe(true) +}) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsEval_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsEval_test.res index 355c69ea..6f232d0e 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsEval_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsEval_test.res @@ -1,5 +1,5 @@ open ReducerInterface.ExpressionValue -module MathJs = Reducer.MathJs +module MathJs = Reducer_MathJs module ErrorValue = Reducer.ErrorValue open Jest diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsParse_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsParse_test.res index 6282c14d..b085eeb3 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsParse_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_MathJs/Reducer_MathJsParse_test.res @@ -1,4 +1,4 @@ -module Parse = Reducer.MathJs.Parse +module Parse = Reducer_MathJs.Parse module Result = Belt.Result open Jest @@ -18,8 +18,14 @@ module MySkip = { 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 paranthesis", () => { + describe("literals operators parenthesis", () => { testParse("1", "1") testParse("'hello'", "'hello'") testParse("true", "true") @@ -40,15 +46,15 @@ describe("MathJs parse", () => { }) describe("functions", () => { - MySkip.testParse("identity(x) = x", "???") - MySkip.testParse("identity(x)", "???") + 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']") - MySkip.testParse("range(0, 4)", "range(0, 4)") + testParse("range(0, 4)", "range(0, 4)") testDescriptionParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]") }) @@ -58,11 +64,6 @@ describe("MathJs parse", () => { }) describe("comments", () => { - MySkip.testDescriptionParse("define", "# This is a comment", "???") - }) - - describe("if statement", () => { - // TODO Tertiary operator instead - MySkip.testDescriptionParse("define", "if (true) { 1 } else { 0 }", "???") + testDescriptionParse("define", "1 # This is a comment", "1") }) }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res index 581f6c8a..766acd43 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res @@ -1,40 +1,31 @@ -module Expression = Reducer.Expression +module ExpressionT = Reducer_Expression_T module ExpressionValue = ReducerInterface.ExpressionValue +module ErrorValue = Reducer_ErrorValue open Jest open Expect +let unwrapRecord = rValue => + rValue->Belt.Result.flatMap(value => + switch value { + | ExpressionValue.EvRecord(aRecord) => Ok(aRecord) + | _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error + } + ) + let expectParseToBe = (expr: string, answer: string) => - Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer) - -let expectParseOuterToBe = (expr: string, answer: string) => - Reducer.parseOuter(expr)->Expression.toStringResult->expect->toBe(answer) - -let expectParsePartialToBe = (expr: string, answer: string) => - Reducer.parsePartial(expr)->Expression.toStringResult->expect->toBe(answer) + Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer) let expectEvalToBe = (expr: string, answer: string) => Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => - Reducer.evaluateUsingExternalBindings(expr, bindings) + Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None) ->ExpressionValue.toStringResult ->expect ->toBe(answer) -let expectEvalPartialBindingsToBe = ( - expr: string, - bindings: Reducer.externalBindings, - answer: string, -) => - Reducer.evaluatePartialUsingExternalBindings(expr, bindings) - ->ExpressionValue.toStringResultRecord - ->expect - ->toBe(answer) - let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) -let testParseOuterToBe = (expr, answer) => test(expr, () => expectParseOuterToBe(expr, answer)) -let testParsePartialToBe = (expr, answer) => test(expr, () => expectParsePartialToBe(expr, answer)) let testDescriptionParseToBe = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer)) @@ -42,34 +33,16 @@ let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answe let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer)) let testEvalBindingsToBe = (expr, bindingsList, answer) => test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) -let testEvalPartialBindingsToBe = (expr, bindingsList, answer) => - test(expr, () => expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) module MySkip = { let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) - let testParseOuterToBe = (expr, answer) => - Skip.test(expr, () => expectParseOuterToBe(expr, answer)) - let testParsePartialToBe = (expr, answer) => - Skip.test(expr, () => expectParsePartialToBe(expr, answer)) let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer)) let testEvalBindingsToBe = (expr, bindingsList, answer) => Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) - let testEvalPartialBindingsToBe = (expr, bindingsList, answer) => - Skip.test(expr, () => - expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer) - ) } module MyOnly = { let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) - let testParseOuterToBe = (expr, answer) => - Only.test(expr, () => expectParseOuterToBe(expr, answer)) - let testParsePartialToBe = (expr, answer) => - Only.test(expr, () => expectParsePartialToBe(expr, answer)) let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer)) let testEvalBindingsToBe = (expr, bindingsList, answer) => Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) - let testEvalPartialBindingsToBe = (expr, bindingsList, answer) => - Only.test(expr, () => - expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer) - ) } diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestMacroHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestMacroHelpers.res new file mode 100644 index 00000000..330f7da0 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestMacroHelpers.res @@ -0,0 +1,82 @@ +open Jest +open Expect + +module Bindings = Reducer_Expression_Bindings +module Expression = Reducer_Expression +module ExpressionValue = ReducerInterface_ExpressionValue +module ExpressionWithContext = Reducer_ExpressionWithContext +module Macro = Reducer_Expression_Macro +module T = Reducer_Expression_T + +let testMacro_ = ( + tester, + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedCode: string, +) => { + let bindings = Belt.Map.String.fromArray(bindArray) + tester(expr->T.toString, () => + expr + ->Macro.expandMacroCall( + bindings, + ExpressionValue.defaultEnvironment, + Expression.reduceExpression, + ) + ->ExpressionWithContext.toStringResult + ->expect + ->toEqual(expectedCode) + ) +} + +let testMacroEval_ = ( + tester, + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedValue: string, +) => { + let bindings = Belt.Map.String.fromArray(bindArray) + tester(expr->T.toString, () => + expr + ->Macro.doMacroCall(bindings, ExpressionValue.defaultEnvironment, Expression.reduceExpression) + ->ExpressionValue.toStringResult + ->expect + ->toEqual(expectedValue) + ) +} + +let testMacro = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedExpr: string, +) => testMacro_(test, bindArray, expr, expectedExpr) +let testMacroEval = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedValue: string, +) => testMacroEval_(test, bindArray, expr, expectedValue) + +module MySkip = { + let testMacro = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedExpr: string, + ) => testMacro_(Skip.test, bindArray, expr, expectedExpr) + let testMacroEval = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedValue: string, + ) => testMacroEval_(Skip.test, bindArray, expr, expectedValue) +} + +module MyOnly = { + let testMacro = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedExpr: string, + ) => testMacro_(Only.test, bindArray, expr, expectedExpr) + let testMacroEval = ( + bindArray: array<(string, ExpressionValue.expressionValue)>, + expr: T.expression, + expectedValue: string, + ) => testMacroEval_(Only.test, bindArray, expr, expectedValue) +} diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_debugging_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_debugging_test.res new file mode 100644 index 00000000..f005c1fc --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_debugging_test.res @@ -0,0 +1,16 @@ +open Jest +open Reducer_TestHelpers + +/* + You can wrap around any expression with inspect(expr) to log the value of that expression. + This is useful for debugging. inspect(expr) returns the value of expr, but also prints it out. + + There is a second version of inspect that takes a label, which will print out the label and the value. + + inspectPerformace(expr, label) will print out the value of expr, the label, and the time it took to evaluate expr. +*/ +describe("Debugging", () => { + testEvalToBe("inspect(1)", "Ok(1)") + testEvalToBe("inspect(1, \"one\")", "Ok(1)") + testEvalToBe("inspectPerformance(1, \"one\")", "Ok(1)") +}) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res index ce834ed1..3a903343 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res @@ -1,60 +1,63 @@ +// TODO: Reimplement with usual parse open Jest open Reducer_TestHelpers -describe("Parse for Bindings", () => { - testParseOuterToBe("x", "Ok((:$$bindExpression (:$$bindings) :x))") - testParseOuterToBe("x+1", "Ok((:$$bindExpression (:$$bindings) (:add :x 1)))") - testParseOuterToBe( - "y = x+1; y", - "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))", - ) -}) +// describe("Parse 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("Parse Partial", () => { +// testParsePartialToBe( +// "x", +// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))", +// ) +// testParsePartialToBe( +// "y=x", +// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y :x)) (:$exportVariablesExpression)))", +// ) +// testParsePartialToBe( +// "y=x+1", +// "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$exportVariablesExpression)))", +// ) +// testParsePartialToBe( +// "y = x+1; z = y", +// "Ok((:$$bindExpression (:$$bindStatement (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$let :z :y)) (:$exportVariablesExpression)))", +// ) +// }) describe("Eval with Bindings", () => { testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)") testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)") + testParseToBe("y = x+1; y", "Ok((:$$block (:$$block (:$let :y (:add :x 1)) :y)))") testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)") + testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})") }) /* Partial code is a partial code fragment that is cut out from a larger code. Therefore it does not end with an expression. */ -describe("Eval Partial", () => { - testEvalPartialBindingsToBe( - // A partial cannot end with an expression - "x", - list{("x", ExpressionValue.EvNumber(1.))}, - "Error(Assignment expected)", - ) - testEvalPartialBindingsToBe("y=x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1, y: 1})") - testEvalPartialBindingsToBe( - "y=x+1", - list{("x", ExpressionValue.EvNumber(1.))}, - "Ok({x: 1, y: 2})", - ) - testEvalPartialBindingsToBe( - "y = x+1; z = y", - list{("x", ExpressionValue.EvNumber(1.))}, - "Ok({x: 1, y: 2, z: 2})", - ) -}) +// 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})", +// ) +// }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_functionAssignment_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_functionAssignment_test.res new file mode 100644 index 00000000..bb3e2220 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_functionAssignment_test.res @@ -0,0 +1,12 @@ +open Jest +open Reducer_TestHelpers + +describe("Parse function assignment", () => { + 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))))))") + //MathJs does not allow blocks in function definitions +}) + +describe("Evaluate function assignment", () => { + testEvalToBe("f(x)=x; f(1)", "Ok(1)") +}) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_functionTricks_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_functionTricks_test.res new file mode 100644 index 00000000..284364e6 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_functionTricks_test.res @@ -0,0 +1,77 @@ +open Jest +open Reducer_TestHelpers + +describe("Arity check", () => { + testEvalToBe("f(x,y) = x + y; f(1,2)", "Ok(3)") + testEvalToBe( + "f(x,y) = x + y; f(1)", + "Error(2 arguments expected. Instead 1 argument(s) were passed.)", + ) + testEvalToBe( + "f(x,y) = x + y; f(1,2,3)", + "Error(2 arguments expected. Instead 3 argument(s) were passed.)", + ) + testEvalToBe( + "f(x,y)=x+y; f(1,2,3,4)", + "Error(2 arguments expected. Instead 4 argument(s) were passed.)", + ) + testEvalToBe( + "f(x,y)=x+y; f(1)", + "Error(2 arguments expected. Instead 1 argument(s) were passed.)", + ) + testEvalToBe( + "f(x,y)=x(y); f(f)", + "Error(2 arguments expected. Instead 1 argument(s) were passed.)", + ) + testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))") + testEvalToBe( + "f(x,y)=x(y); f(z)", + "Error(2 arguments expected. Instead 1 argument(s) were passed.)", + ) +}) + +describe("symbol not defined", () => { + testEvalToBe("f(x)=x(y); f(f)", "Error(y is not defined)") + testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))") + testEvalToBe("f(x)=x(y); f(z)", "Error(z is not defined)") + testEvalToBe("f(x)=x(y); f(2)", "Error(2 is not a function)") + testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)") +}) + +describe("call and bindings", () => { + testEvalToBe("f(x)=x+1", "Ok({f: lambda(x=>internal code)})") + testEvalToBe("f(x)=x+1; f(1)", "Ok(2)") + testEvalToBe("f=1;y=2", "Ok({f: 1,y: 2})") + testEvalToBe("f(x)=x+1; y=f(1)", "Ok({f: lambda(x=>internal code),y: 2})") + testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)") + testEvalToBe("f(x)=x+1; y=f(1); z=f(1)", "Ok({f: lambda(x=>internal code),y: 2,z: 2})") + testEvalToBe( + "f(x)=x+1; g(x)=f(x)+1", + "Ok({f: lambda(x=>internal code),g: lambda(x=>internal code)})", + ) + testParseToBe( + "f=99; g(x)=f; g(2)", + "Ok((:$$block (:$$block (:$let :f 99) (:$let :g (:$$lambda [x] (:$$block :f))) (:g 2))))", + ) + testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)") + testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") + testEvalToBe( + "f(x)=x+1; g(x)=f(x)+1; y=g(2)", + "Ok({f: lambda(x=>internal code),g: lambda(x=>internal code),y: 4})", + ) + testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)") +}) + +describe("function trics", () => { + testParseToBe( + "f(x)=f(y)=2; f(2)", + "Ok((:$$block (:$$block (:$let :f (:$$lambda [x] (:$$block (:$let :f (:$$lambda [y] (:$$block 2)))))) (:f 2))))", + ) + testEvalToBe("f(x)=f(y)=2; f(2)", "Ok({f: lambda(y=>internal code),x: 2})") + testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)") + testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})") + MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout? + MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters + MySkip.testEvalToBe("myadd(x,y)=x+y; z=[add]; z[0](3,2)", "????") //TODO: to fix with new parser + MySkip.testEvalToBe("myaddd(x,y)=x+y; z={x: add}; z.x(3,2)", "????") //TODO: to fix with new parser +}) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index 5514b67c..80468c0f 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -10,46 +10,39 @@ describe("reducer using mathjs parse", () => { // Those tests toString that we are converting mathjs parse tree to what we need describe("expressions", () => { - testParseToBe("1", "Ok(1)") - testParseToBe("(1)", "Ok(1)") - testParseToBe("1+2", "Ok((:add 1 2))") - testParseToBe("1+2", "Ok((:add 1 2))") - testParseToBe("1+2", "Ok((:add 1 2))") - testParseToBe("1+2*3", "Ok((:add 1 (:multiply 2 3)))") + 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(())") - testParseToBe("[1, 2, 3]", "Ok((1 2 3))") - testParseToBe("['hello', 'world']", "Ok(('hello' 'world'))") - testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$atIndex (0 1 2) (1)))") + 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((:$constructRecord (('a' 1) ('b' 2))))") + testDescriptionParseToBe( + "define", + "{a: 1, b: 2}", + "Ok((:$$block (:$constructRecord (('a' 1) ('b' 2)))))", + ) testDescriptionParseToBe( "use", "{a: 1, b: 2}.a", - "Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))", + "Ok((:$$block (:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a'))))", ) }) describe("multi-line", () => { - testParseToBe("1; 2", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) 1) 2))") - testParseToBe( - "1+1; 2+1", - "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:add 1 1)) (:add 2 1)))", - ) + 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((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x 1)) :x))", - ) - testParseToBe( - "x=1+1; x+1", - "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x (:add 1 1))) (:add :x 1)))", - ) + 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))))") }) }) @@ -70,13 +63,13 @@ describe("eval", () => { }) describe("arrays", () => { test("empty array", () => expectEvalToBe("[]", "Ok([])")) - testEvalToBe("[1, 2, 3]", "Ok([1, 2, 3])") - testEvalToBe("['hello', 'world']", "Ok(['hello', 'world'])") + testEvalToBe("[1, 2, 3]", "Ok([1,2,3])") + testEvalToBe("['hello', 'world']", "Ok(['hello','world'])") testEvalToBe("([0,1,2])[1]", "Ok(1)") testDescriptionEvalToBe("index not found", "([0,1,2])[10]", "Error(Array index not found: 10)") }) 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 not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)")) }) @@ -91,7 +84,7 @@ describe("eval", () => { testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") testEvalToBe("1; x=1", "Error(Assignment expected)") testEvalToBe("1; 1", "Error(Assignment expected)") - testEvalToBe("x=1; x=1", "Error(Expression expected)") + testEvalToBe("x=1; x=1", "Ok({x: 1})") }) }) diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res index 4df843bb..bb330479 100644 --- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res +++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res @@ -119,27 +119,34 @@ describe("eval on distribution functions", () => { describe("parse on distribution functions", () => { describe("power", () => { - testParse("normal(5,2) ^ normal(5,1)", "Ok((:pow (:normal 5 2) (:normal 5 1)))") - testParse("3 ^ normal(5,1)", "Ok((:pow 3 (:normal 5 1)))") - testParse("normal(5,2) ^ 3", "Ok((:pow (:normal 5 2) 3))") + 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("normal(5,2) ^ 3", "Ok((:$$block (:pow (:normal 5 2) 3)))") }) describe("subtraction", () => { - testParse("10 - normal(5,1)", "Ok((:subtract 10 (:normal 5 1)))") - testParse("normal(5,1) - 10", "Ok((:subtract (:normal 5 1) 10))") + testParse("10 - normal(5,1)", "Ok((:$$block (:subtract 10 (:normal 5 1))))") + testParse("normal(5,1) - 10", "Ok((:$$block (:subtract (:normal 5 1) 10)))") }) 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((: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))))" ) - testParse("normal(5,2) .* normal(5,1)", "Ok((:dotMultiply (:normal 5 2) (:normal 5 1)))") - testParse("normal(5,2) ./ normal(5,1)", "Ok((:dotDivide (:normal 5 2) (:normal 5 1)))") - testParse("normal(5,2) .^ normal(5,1)", "Ok((:dotPow (:normal 5 2) (:normal 5 1)))") + testParse( + "normal(5,2) .* normal(5,1)", + "Ok((:$$block (:dotMultiply (:normal 5 2) (:normal 5 1))))", + ) + testParse( + "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))))") }) describe("equality", () => { - testParse("5 == normal(5,2)", "Ok((:equal 5 (:normal 5 2)))") + testParse("5 == normal(5,2)", "Ok((:$$block (:equal 5 (:normal 5 2))))") }) describe("pointwise adding two normals", () => { testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_ExpressionValue_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_ExpressionValue_test.res index fc2d555e..888aca7e 100644 --- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_ExpressionValue_test.res +++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_ExpressionValue_test.res @@ -3,9 +3,9 @@ open Jest open Expect describe("ExpressionValue", () => { - test("argsToString", () => expect([EvNumber(1.), EvString("a")]->argsToString)->toBe("1, 'a'")) + test("argsToString", () => expect([EvNumber(1.), EvString("a")]->argsToString)->toBe("1,'a'")) test("toStringFunctionCall", () => - expect(("fn", [EvNumber(1.), EvString("a")])->toStringFunctionCall)->toBe("fn(1, 'a')") + expect(("fn", [EvNumber(1.), EvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')") ) }) diff --git a/packages/squiggle-lang/package.json b/packages/squiggle-lang/package.json index fa77db19..98748e63 100644 --- a/packages/squiggle-lang/package.json +++ b/packages/squiggle-lang/package.json @@ -4,7 +4,9 @@ "homepage": "https://squiggle-language.com", "license": "MIT", "scripts": { - "build": "rescript build -with-deps && tsc", + "build": "yarn build:rescript && yarn build:typescript", + "build:rescript": "rescript build -with-deps", + "build:typescript": "tsc", "bundle": "webpack", "start": "rescript build -w -with-deps", "clean": "rescript clean && rm -r dist", diff --git a/packages/squiggle-lang/src/js/distribution.ts b/packages/squiggle-lang/src/js/distribution.ts index 603dfaa9..44c15882 100644 --- a/packages/squiggle-lang/src/js/distribution.ts +++ b/packages/squiggle-lang/src/js/distribution.ts @@ -3,7 +3,7 @@ import { genericDist, continuousShape, discreteShape, - samplingParams, + environment, distributionError, toPointSet, distributionErrorToString, @@ -51,9 +51,9 @@ export type shape = { export class Distribution { t: genericDist; - env: samplingParams; + env: environment; - constructor(t: genericDist, env: samplingParams) { + constructor(t: genericDist, env: environment) { this.t = t; this.env = env; return this; diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index c884164a..589548e3 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -1,8 +1,10 @@ import * as _ from "lodash"; import { samplingParams, - evaluateUsingExternalBindings, + environment, + defaultEnvironment, evaluatePartialUsingExternalBindings, + evaluateUsingOptions, externalBindings, expressionValue, errorValue, @@ -39,33 +41,34 @@ export let defaultSamplingInputs: samplingParams = { export function run( squiggleString: string, bindings?: externalBindings, - samplingInputs?: samplingParams, + environment?: environment, imports?: jsImports ): result { let b = bindings ? bindings : defaultBindings; let i = imports ? imports : defaultImports; - let si: samplingParams = samplingInputs - ? samplingInputs - : defaultSamplingInputs; - - let result: result = - evaluateUsingExternalBindings(squiggleString, mergeImports(b, i)); - return resultMap(result, (x) => createTsExport(x, si)); + let e = environment ? environment : defaultEnvironment; + let res: result = evaluateUsingOptions( + { externalBindings: mergeImports(b, i), environment: e }, + squiggleString + ); + return resultMap(res, (x) => createTsExport(x, e)); } // Run Partial. A partial is a block of code that doesn't return a value export function runPartial( squiggleString: string, bindings?: externalBindings, - _samplingInputs?: samplingParams, + environment?: environment, imports?: jsImports ): result { let b = bindings ? bindings : defaultBindings; let i = imports ? imports : defaultImports; + let e = environment ? environment : defaultEnvironment; return evaluatePartialUsingExternalBindings( squiggleString, - mergeImports(b, i) + mergeImports(b, i), + e ); } @@ -89,7 +92,7 @@ export let defaultBindings: externalBindings = {}; function createTsExport( x: expressionValue, - sampEnv: samplingParams + environment: environment ): squiggleExpression { switch (x.tag) { case "EvArray": @@ -108,7 +111,10 @@ function createTsExport( return tag( "record", _.mapValues(arrayItem.value, (recordValue: unknown) => - convertRawToTypescript(recordValue as rescriptExport, sampEnv) + convertRawToTypescript( + recordValue as rescriptExport, + environment + ) ) ); case "EvArray": @@ -116,20 +122,24 @@ function createTsExport( return tag( "array", y.map((childArrayItem) => - convertRawToTypescript(childArrayItem, sampEnv) + convertRawToTypescript(childArrayItem, environment) ) ); default: - return createTsExport(arrayItem, sampEnv); + return createTsExport(arrayItem, environment); } }) ); + case "EvArrayString": + return tag("arraystring", x.value); case "EvBool": return tag("boolean", x.value); case "EvCall": return tag("call", x.value); + case "EvLambda": + return tag("lambda", x.value); case "EvDistribution": - return tag("distribution", new Distribution(x.value, sampEnv)); + return tag("distribution", new Distribution(x.value, environment)); case "EvNumber": return tag("number", x.value); case "EvRecord": @@ -137,7 +147,7 @@ function createTsExport( let result: tagged<"record", { [key: string]: squiggleExpression }> = tag( "record", _.mapValues(x.value, (x: unknown) => - convertRawToTypescript(x as rescriptExport, sampEnv) + convertRawToTypescript(x as rescriptExport, environment) ) ); return result; diff --git a/packages/squiggle-lang/src/js/rescript_interop.ts b/packages/squiggle-lang/src/js/rescript_interop.ts index b017699f..45f4124b 100644 --- a/packages/squiggle-lang/src/js/rescript_interop.ts +++ b/packages/squiggle-lang/src/js/rescript_interop.ts @@ -3,10 +3,11 @@ import { mixedShape, sampleSetDist, genericDist, - samplingParams, + environment, symbolicDist, discreteShape, continuousShape, + lambdaValue, } from "../rescript/TypescriptInterface.gen"; import { Distribution } from "./distribution"; import { tagged, tag } from "./types"; @@ -19,31 +20,39 @@ export type rescriptExport = _0: rescriptExport[]; } | { - TAG: 1; // EvBool + TAG: 1; // EvString + _0: string[]; + } + | { + TAG: 2; // EvBool _0: boolean; } | { - TAG: 2; // EvCall + TAG: 3; // EvCall _0: string; } | { - TAG: 3; // EvDistribution + TAG: 4; // EvDistribution _0: rescriptDist; } | { - TAG: 4; // EvNumber + TAG: 5; // EvLambda + _0: lambdaValue; + } + | { + TAG: 6; // EvNumber _0: number; } | { - TAG: 5; // EvRecord + TAG: 7; // EvRecord _0: { [key: string]: rescriptExport }; } | { - TAG: 6; // EvString + TAG: 8; // EvString _0: string; } | { - TAG: 7; // EvSymbol + TAG: 9; // EvSymbol _0: string; }; @@ -70,7 +79,9 @@ export type squiggleExpression = | tagged<"symbol", string> | tagged<"string", string> | tagged<"call", string> + | tagged<"lambda", lambdaValue> | tagged<"array", squiggleExpression[]> + | tagged<"arraystring", string[]> | tagged<"boolean", boolean> | tagged<"distribution", Distribution> | tagged<"number", number> @@ -78,36 +89,40 @@ export type squiggleExpression = export function convertRawToTypescript( result: rescriptExport, - sampEnv: samplingParams + environment: environment ): squiggleExpression { switch (result.TAG) { case 0: // EvArray return tag( "array", - result._0.map((x) => convertRawToTypescript(x, sampEnv)) + result._0.map((x) => convertRawToTypescript(x, environment)) ); - case 1: // EvBool + case 1: // EvArrayString + return tag("arraystring", result._0); + case 2: // EvBool return tag("boolean", result._0); - case 2: // EvCall + case 3: // EvCall return tag("call", result._0); - case 3: // EvDistribution + case 4: // EvDistribution return tag( "distribution", new Distribution( convertRawDistributionToGenericDist(result._0), - sampEnv + environment ) ); - case 4: // EvNumber + case 5: // EvDistribution + return tag("lambda", result._0); + case 6: // EvNumber return tag("number", result._0); - case 5: // EvRecord + case 7: // EvRecord return tag( "record", - _.mapValues(result._0, (x) => convertRawToTypescript(x, sampEnv)) + _.mapValues(result._0, (x) => convertRawToTypescript(x, environment)) ); - case 6: // EvString + case 8: // EvString return tag("string", result._0); - case 7: // EvSymbol + case 9: // EvSymbol return tag("symbol", result._0); } } @@ -141,15 +156,15 @@ export type jsValue = export function jsValueToBinding(value: jsValue): rescriptExport { if (typeof value === "boolean") { - return { TAG: 1, _0: value as boolean }; + return { TAG: 2, _0: value as boolean }; } else if (typeof value === "string") { - return { TAG: 6, _0: value as string }; + return { TAG: 8, _0: value as string }; } else if (typeof value === "number") { - return { TAG: 4, _0: value as number }; + return { TAG: 6, _0: value as number }; } else if (Array.isArray(value)) { return { TAG: 0, _0: value.map(jsValueToBinding) }; } else { // Record - return { TAG: 5, _0: _.mapValues(value, jsValueToBinding) }; + return { TAG: 7, _0: _.mapValues(value, jsValueToBinding) }; } } diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res index 5f07c6a8..1af12e1a 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res @@ -9,6 +9,11 @@ type env = { xyPointLength: int, } +let defaultEnv = { + sampleCount: 10000, + xyPointLength: 10000, +} + type outputType = | Dist(genericDist) | Float(float) diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi index fcaeb5e4..77aa546b 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi @@ -4,6 +4,9 @@ type env = { xyPointLength: int, } +@genType +let defaultEnv: env + open DistributionTypes @genType diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res index 0d413bf4..a9151a8f 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res @@ -114,6 +114,7 @@ module DistributionOperation = { | ToFloat(#Mean) => `mean` | ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})` | ToFloat(#Sample) => `sample` + | ToFloat(#IntegralSum) => `integralSum` | ToDist(Normalize) => `normalize` | ToDist(ToPointSet) => `toPointSet` | ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})` diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer.res index d2e4858f..1ee57529 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer.res @@ -1,15 +1,11 @@ -module Dispatch = Reducer_Dispatch module ErrorValue = Reducer_ErrorValue module Expression = Reducer_Expression -module Extra = Reducer_Extra -module Js = Reducer_Js -module MathJs = Reducer_MathJs -type expressionValue = Reducer_Expression.expressionValue -type externalBindings = Expression.externalBindings -let evaluate = Expression.eval -let evaluateUsingExternalBindings = Expression.evalUsingExternalBindings -let evaluatePartialUsingExternalBindings = Expression.evalPartialUsingExternalBindings +type environment = ReducerInterface_ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue +type expressionValue = ReducerInterface_ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings +let evaluate = Expression.evaluate +let evaluateUsingOptions = Expression.evaluateUsingOptions +let evaluatePartialUsingExternalBindings = Expression.evaluatePartialUsingExternalBindings let parse = Expression.parse -let parseOuter = Expression.parseOuter -let parsePartial = Expression.parsePartial diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi b/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi index 8bbfc0b5..71b394fb 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi @@ -1,26 +1,28 @@ -module Dispatch = Reducer_Dispatch module ErrorValue = Reducer_ErrorValue module Expression = Reducer_Expression -module Extra = Reducer_Extra -module Js = Reducer_Js -module MathJs = Reducer_MathJs +@genType +type environment = ReducerInterface_ExpressionValue.environment +@genType +type errorValue = Reducer_ErrorValue.errorValue @genType type expressionValue = ReducerInterface_ExpressionValue.expressionValue @genType type externalBindings = ReducerInterface_ExpressionValue.externalBindings + @genType -let evaluate: string => result -@genType -let evaluateUsingExternalBindings: ( +let evaluateUsingOptions: ( + ~environment: option, + ~externalBindings: option, string, - externalBindings, -) => result +) => result @genType let evaluatePartialUsingExternalBindings: ( string, - externalBindings, -) => result -let parse: string => result -let parseOuter: string => result -let parsePartial: string => result + QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings, + QuriSquiggleLang.ReducerInterface_ExpressionValue.environment, +) => result +@genType +let evaluate: string => result + +let parse: string => result diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res index 21c91dc2..25fc05a7 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res @@ -1,5 +1,6 @@ module ExternalLibrary = ReducerInterface.ExternalLibrary module MathJs = Reducer_MathJs +module Bindings = Reducer_Expression_Bindings open ReducerInterface.ExpressionValue open Reducer_ErrorValue @@ -11,7 +12,7 @@ open Reducer_ErrorValue exception TestRescriptException -let callInternal = (call: functionCall): result<'b, errorValue> => { +let callInternal = (call: functionCall, _environment): result<'b, errorValue> => { let callMathJs = (call: functionCall): result<'b, errorValue> => switch call { | ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests @@ -20,12 +21,12 @@ let callInternal = (call: functionCall): result<'b, errorValue> => { } let constructRecord = arrayOfPairs => { - Belt.Array.map(arrayOfPairs, pairValue => { + Belt.Array.map(arrayOfPairs, pairValue => switch pairValue { | EvArray([EvString(key), valueValue]) => (key, valueValue) | _ => ("wrong key type", pairValue->toStringWithType->EvString) } - }) + ) ->Js.Dict.fromArray ->EvRecord ->Ok @@ -43,16 +44,58 @@ let callInternal = (call: functionCall): result<'b, errorValue> => { | None => RERecordPropertyNotFound("Record property not found", sIndex)->Error } + let inspect = (value: expressionValue) => { + Js.log(value->toString) + value->Ok + } + + let inspectLabel = (value: expressionValue, label: string) => { + Js.log(`${label}: ${value->toString}`) + value->Ok + } + + /* + NOTE: This function is cancelled. The related issue is + https://github.com/webpack/webpack/issues/13435 + */ + let inspectPerformance = (value: expressionValue, label: string) => { + // let _ = %raw("{performance} = require('perf_hooks')") + // let start = %raw(`performance.now()`) + // let finish = %raw(`performance.now()`) + // let performance = finish - start + // Js.log(`${label}: ${value->toString} performance: ${Js.String.make(performance)}ms`) + // TODO find a way of failing the hook gracefully, also needs a block parameter + Js.log(`${label}: ${value->toString}`) + value->Ok + } + + let doSetBindings = ( + externalBindings: externalBindings, + symbol: string, + value: expressionValue, + ) => { + Bindings.fromExternalBindings(externalBindings) + ->Belt.Map.String.set(symbol, value) + ->Bindings.toExternalBindings + ->EvRecord + ->Ok + } + + let doExportBindings = (externalBindings: externalBindings) => EvRecord(externalBindings)->Ok + switch call { - // | ("$constructRecord", pairArray) - // | ("$atIndex", [EvArray(anArray), EvNumber(fIndex)]) => arrayAtIndex(anArray, fIndex) - // | ("$atIndex", [EvRecord(aRecord), EvString(sIndex)]) => recordAtIndex(aRecord, sIndex) - | ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs) | ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) => arrayAtIndex(aValueArray, fIndex) | ("$atIndex", [EvRecord(dict), EvArray([EvString(sIndex)])]) => recordAtIndex(dict, sIndex) | ("$atIndex", [obj, index]) => (toStringWithType(obj) ++ "??~~~~" ++ toStringWithType(index))->EvString->Ok + | ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs) + | ("inspect", [value, EvString(label)]) => inspectLabel(value, label) + | ("inspect", [value]) => inspect(value) + | ("inspectPerformance", [value, EvString(label)]) => inspectPerformance(value, label) + | ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) => + doSetBindings(externalBindings, symbol, value) + | ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings) | call => callMathJs(call) } } @@ -60,12 +103,12 @@ let callInternal = (call: functionCall): result<'b, errorValue> => { /* Reducer uses Result monad while reducing expressions */ -let dispatch = (call: functionCall): result => +let dispatch = (call: functionCall, environment): result => try { let (fn, args) = call // There is a bug that prevents string match in patterns // So we have to recreate a copy of the string - ExternalLibrary.dispatch((Js.String.make(fn), args), callInternal) + ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternal) } catch { | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | _ => RETodo("unhandled rescript exception")->Error diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res index b7df0ff3..c106c0ff 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res @@ -3,120 +3,170 @@ they take expressions as parameters and return a new expression. Macros are used to define language building blocks. They are like Lisp macros. */ +module Bindings = Reducer_Expression_Bindings module ExpressionT = Reducer_Expression_T module ExpressionValue = ReducerInterface.ExpressionValue +module ExpressionWithContext = Reducer_ExpressionWithContext module Result = Belt.Result +open Reducer_Expression_ExpressionBuilder -open Reducer_ErrorValue - +type environment = ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue type expression = ExpressionT.expression - -type reducerFn = ( - expression, - ExpressionT.bindings, -) => result +type expressionValue = ExpressionValue.expressionValue +type expressionWithContext = ExpressionWithContext.expressionWithContext let dispatchMacroCall = ( - list: list, + macroExpression: expression, bindings: ExpressionT.bindings, - reduceExpression: reducerFn, -): result => { - let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result< - expression, - errorValue, - > => - switch expression { - | ExpressionT.EValue(EvSymbol(aSymbol)) => - switch bindings->Belt.Map.String.get(aSymbol) { - | Some(boundExpression) => boundExpression->Ok - | None => RESymbolNotFound(aSymbol)->Error - } - | ExpressionT.EValue(_) => expression->Ok - | ExpressionT.EBindings(_) => expression->Ok - | ExpressionT.EList(list) => { - let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) => - racc->Result.flatMap(acc => { - each - ->replaceSymbols(bindings) - ->Result.flatMap(newNode => { - acc->Belt.List.add(newNode)->Ok - }) - }) - ) - racc->Result.map(acc => acc->ExpressionT.EList) - } - } - - let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => { + environment, + reduceExpression: ExpressionT.reducerFn, +): result => { + let doBindStatement = (bindingExpr: expression, statement: expression, environment) => switch statement { - | ExpressionT.EList(list{ - ExpressionT.EValue(EvCall("$let")), - ExpressionT.EValue(EvSymbol(aSymbol)), - expressionToReduce, - }) => { - let rNewExpressionToReduce = replaceSymbols(expressionToReduce, bindings) + | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => { + let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment) - let rNewValue = - rNewExpressionToReduce->Result.flatMap(newExpressionToReduce => - reduceExpression(newExpressionToReduce, bindings) + rExternalBindingsValue->Result.flatMap(externalBindingsValue => { + let newBindings = Bindings.fromValue(externalBindingsValue) + + // Js.log( + // `bindStatement ${Bindings.toString(newBindings)}<==${ExpressionT.toString( + // bindingExpr, + // )} statement: $let ${ExpressionT.toString(symbolExpr)}=${ExpressionT.toString( + // statement, + // )}`, + // ) + + let rNewStatement = Bindings.replaceSymbols(newBindings, statement) + rNewStatement->Result.map(newStatement => + ExpressionWithContext.withContext( + eFunction( + "$setBindings", + list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement}, + ), + newBindings, + ) ) - - let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue)) - rNewExpression->Result.map(newExpression => - Belt.Map.String.set(bindings, aSymbol, newExpression)->ExpressionT.EBindings - ) + }) } | _ => REAssignmentExpected->Error } - } - let doExportVariableExpression = (bindings: ExpressionT.bindings) => { - let emptyDictionary: Js.Dict.t = Js.Dict.empty() - let reducedBindings = bindings->Belt.Map.String.keep((_key, value) => - switch value { - | ExpressionT.EValue(_) => true - | _ => false - } - ) - let externalBindings = reducedBindings->Belt.Map.String.reduce(emptyDictionary, ( - acc, - key, - expressionValue, - ) => { - let value = switch expressionValue { - | EValue(aValue) => aValue - | _ => EvSymbol("internal") - } - Js.Dict.set(acc, key, value) - acc - }) - externalBindings->ExpressionValue.EvRecord->ExpressionT.EValue->Ok - } + let doBindExpression = (bindingExpr: expression, statement: expression, environment): result< + expressionWithContext, + errorValue, + > => + switch statement { + | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => { + let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment) - let doBindExpression = (expression: expression, bindings: ExpressionT.bindings) => - switch expression { - | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), ..._}) => - REExpressionExpected->Error - | ExpressionT.EList(list{ExpressionT.EValue(EvCall("$exportVariablesExpression"))}) => - doExportVariableExpression(bindings) - | _ => replaceSymbols(expression, bindings) + rExternalBindingsValue->Result.flatMap(externalBindingsValue => { + let newBindings = Bindings.fromValue(externalBindingsValue) + let rNewStatement = Bindings.replaceSymbols(newBindings, statement) + rNewStatement->Result.map(newStatement => + ExpressionWithContext.withContext( + eFunction( + "$exportBindings", + list{ + eFunction( + "$setBindings", + list{ + newBindings->Bindings.toExternalBindings->eRecord, + symbolExpr, + newStatement, + }, + ), + }, + ), + newBindings, + ) + ) + }) + } + | _ => { + let rExternalBindingsValue: result = reduceExpression( + bindingExpr, + bindings, + environment, + ) + + rExternalBindingsValue->Result.flatMap(externalBindingsValue => { + let newBindings = Bindings.fromValue(externalBindingsValue) + let rNewStatement = Bindings.replaceSymbols(newBindings, statement) + rNewStatement->Result.map(newStatement => + ExpressionWithContext.withContext(newStatement, newBindings) + ) + }) + } } - switch list { - | list{ExpressionT.EValue(EvCall("$$bindings"))} => bindings->ExpressionT.EBindings->Ok + let doBlock = (exprs: list, _bindings: ExpressionT.bindings, _environment): result< + expressionWithContext, + errorValue, + > => { + let exprsArray = Belt.List.toArray(exprs) + let maxIndex = Js.Array2.length(exprsArray) - 1 + let newStatement = exprsArray->Js.Array2.reducei((acc, statement, index) => + if index == 0 { + if index == maxIndex { + eBindExpressionDefault(statement) + } else { + eBindStatementDefault(statement) + } + } else if index == maxIndex { + eBindExpression(acc, statement) + } else { + eBindStatement(acc, statement) + } + , eSymbol("undefined block")) + ExpressionWithContext.noContext(newStatement)->Ok + } - | list{ - ExpressionT.EValue(EvCall("$$bindStatement")), - ExpressionT.EBindings(bindings), - statement, - } => - doBindStatement(statement, bindings) - | list{ - ExpressionT.EValue(EvCall("$$bindExpression")), - ExpressionT.EBindings(bindings), - expression, - } => - doBindExpression(expression, bindings) - | _ => list->ExpressionT.EList->Ok + let doLambdaDefinition = ( + bindings: ExpressionT.bindings, + parameters: array, + lambdaDefinition: ExpressionT.expression, + ) => + ExpressionWithContext.noContext( + eLambda(parameters, bindings->Bindings.toExternalBindings, lambdaDefinition), + )->Ok + + let expandExpressionList = (aList, bindings: ExpressionT.bindings, environment): result< + expressionWithContext, + errorValue, + > => + switch aList { + | list{ + ExpressionT.EValue(EvCall("$$bindStatement")), + bindingExpr: ExpressionT.expression, + statement, + } => + doBindStatement(bindingExpr, statement, environment) + | list{ExpressionT.EValue(EvCall("$$bindStatement")), statement} => + // bindings of the context are used when there is no binding expression + doBindStatement(eRecord(Bindings.toExternalBindings(bindings)), statement, environment) + | list{ + ExpressionT.EValue(EvCall("$$bindExpression")), + bindingExpr: ExpressionT.expression, + expression, + } => + doBindExpression(bindingExpr, expression, environment) + | list{ExpressionT.EValue(EvCall("$$bindExpression")), expression} => + // bindings of the context are used when there is no binding expression + doBindExpression(eRecord(Bindings.toExternalBindings(bindings)), expression, environment) + | list{ExpressionT.EValue(EvCall("$$block")), ...exprs} => doBlock(exprs, bindings, environment) + | list{ + ExpressionT.EValue(EvCall("$$lambda")), + ExpressionT.EValue(EvArrayString(parameters)), + lambdaDefinition, + } => + doLambdaDefinition(bindings, parameters, lambdaDefinition) + | _ => ExpressionWithContext.noContext(ExpressionT.EList(aList))->Ok + } + + switch macroExpression { + | EList(aList) => expandExpressionList(aList, bindings, environment) + | _ => ExpressionWithContext.noContext(macroExpression)->Ok } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index 96b73fd2..7964c3a4 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -1,15 +1,17 @@ @genType type errorValue = + | REArityError(option, int, int) //TODO: Binding a lambda to a variable should record the variable name in lambda for error reporting | REArrayIndexNotFound(string, int) | REAssignmentExpected + | REDistributionError(DistributionTypes.error) | REExpressionExpected | REFunctionExpected(string) | REJavaScriptExn(option, option) // Javascript Exception | REMacroNotFound(string) + | RENotAFunction(string) | RERecordPropertyNotFound(string, string) | RESymbolNotFound(string) | RESyntaxError(string) - | REDistributionError(DistributionTypes.error) | RETodo(string) // To do type t = errorValue @@ -17,6 +19,10 @@ type t = errorValue @genType let errorToString = err => switch err { + | REArityError(_oFnName, arity, usedArity) => + `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( + usedArity, + )} argument(s) were passed.` | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` | REAssignmentExpected => "Assignment expected" | REExpressionExpected => "Expression expected" @@ -35,6 +41,7 @@ let errorToString = err => answer } | REMacroNotFound(macro) => `Macro not found: ${macro}` + | RENotAFunction(valueString) => `${valueString} is not a function` | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` | RESymbolNotFound(symbolName) => `${symbolName} is not defined` | RESyntaxError(desc) => `Syntax Error: ${desc}` diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res index b45b8e14..e1df1418 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res @@ -1,35 +1,22 @@ +module Bindings = Reducer_Expression_Bindings module BuiltIn = Reducer_Dispatch_BuiltIn +module ExpressionBuilder = Reducer_Expression_ExpressionBuilder module ExpressionValue = ReducerInterface.ExpressionValue module Extra = Reducer_Extra +module Lambda = Reducer_Expression_Lambda +module Macro = Reducer_Expression_Macro module MathJs = Reducer_MathJs module Result = Belt.Result module T = Reducer_Expression_T -open Reducer_ErrorValue +type environment = ReducerInterface_ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue type expression = T.expression -type expressionValue = ExpressionValue.expressionValue +type expressionValue = ReducerInterface_ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings +type internalCode = ReducerInterface_ExpressionValue.internalCode type t = expression -/* - Shows the expression as text of expression -*/ -let rec toString = expression => - switch expression { - | T.EBindings(_) => "$$bound" - | T.EList(aList) => - `(${Belt.List.map(aList, aValue => toString(aValue)) - ->Extra.List.interperse(" ") - ->Belt.List.toArray - ->Js.String.concatMany("")})` - | EValue(aValue) => ExpressionValue.toString(aValue) - } - -let toStringResult = codeResult => - switch codeResult { - | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${Js.String.make(m)})` - } - /* Converts a MathJs code to expression */ @@ -39,148 +26,115 @@ let parse_ = (expr: string, parser, converter): result => let parse = (mathJsCode: string): result => mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode) -let parsePartial = (mathJsCode: string): result => - mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromPartialNode) - -let parseOuter = (mathJsCode: string): result => - mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromOuterNode) - -let defaultBindings: T.bindings = Belt.Map.String.empty - /* Recursively evaluate/reduce the expression (Lisp AST) */ -let rec reduceExpression = (expression: t, bindings: T.bindings): result => { - /* - Macros are like functions but instead of taking values as parameters, - they take expressions as parameters and return a new expression. - Macros are used to define language building blocks. They are like Lisp macros. - */ - let doMacroCall = (list: list, bindings: T.bindings): result => - Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, reduceExpression) +let rec reduceExpression = (expression: t, bindings: T.bindings, environment: environment): result< + expressionValue, + 'e, +> => { + // Js.log(`reduce: ${T.toString(expression)} bindings: ${bindings->Bindings.toString}`) + switch expression { + | T.EValue(value) => value->Ok + | T.EList(list) => + switch list { + | list{EValue(EvCall(fName)), ..._args} => + switch Macro.isMacroName(fName) { + // A macro expands then reduces itself + | true => Macro.doMacroCall(expression, bindings, environment, reduceExpression) + | false => reduceExpressionList(list, bindings, environment) + } + | _ => reduceExpressionList(list, bindings, environment) + } + } +} - /* +and reduceExpressionList = ( + expressions: list, + bindings: T.bindings, + environment: environment, +): result => { + let racc: result, 'e> = expressions->Belt.List.reduceReverse(Ok(list{}), ( + racc, + each: expression, + ) => + racc->Result.flatMap(acc => { + each + ->reduceExpression(bindings, environment) + ->Result.map(newNode => { + acc->Belt.List.add(newNode) + }) + }) + ) + racc->Result.flatMap(acc => acc->reduceValueList(environment)) +} + +/* After reducing each level of expression(Lisp AST), we have a value list to evaluate */ - let reduceValueList = (valueList: list): result => - switch valueList { - | list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch - | _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok - } +and reduceValueList = (valueList: list, environment): result< + expressionValue, + 'e, +> => + switch valueList { + | list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment) - let rec seekMacros = (expression: t, bindings: T.bindings): result => - switch expression { - | T.EValue(_value) => expression->Ok - | T.EBindings(_value) => expression->Ok - | T.EList(list) => { - let racc: result, 'e> = list->Belt.List.reduceReverse(Ok(list{}), ( - racc, - each: expression, - ) => - racc->Result.flatMap(acc => { - each - ->seekMacros(bindings) - ->Result.flatMap(newNode => { - acc->Belt.List.add(newNode)->Ok - }) - }) - ) - racc->Result.flatMap(acc => acc->doMacroCall(bindings)) - } - } + | list{EvLambda(lamdaCall), ...args} => + Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression) + | _ => + valueList + ->Lambda.checkIfReduced + ->Result.flatMap(reducedValueList => + reducedValueList->Belt.List.toArray->ExpressionValue.EvArray->Ok + ) + } - let rec reduceExpandedExpression = (expression: t): result => - switch expression { - | T.EValue(value) => value->Ok - | T.EList(list) => { - let racc: result, 'e> = list->Belt.List.reduceReverse(Ok(list{}), ( - racc, - each: expression, - ) => - racc->Result.flatMap(acc => { - each - ->reduceExpandedExpression - ->Result.flatMap(newNode => { - acc->Belt.List.add(newNode)->Ok - }) - }) - ) - racc->Result.flatMap(acc => acc->reduceValueList) - } - | EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error - } +let evalUsingBindingsExpression_ = (aExpression, bindings, environment): result< + expressionValue, + 'e, +> => reduceExpression(aExpression, bindings, environment) - let rExpandedExpression: result = expression->seekMacros(bindings) - rExpandedExpression->Result.flatMap(expandedExpression => - expandedExpression->reduceExpandedExpression - ) -} +let evaluateUsingOptions = ( + ~environment: option, + ~externalBindings: option, + code: string, +): result => { + let anEnvironment = switch environment { + | Some(env) => env + | None => ReducerInterface_ExpressionValue.defaultEnvironment + } -let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result => - reduceExpression(aExpression, bindings) + let anExternalBindings = switch externalBindings { + | Some(bindings) => bindings + | None => ReducerInterface_ExpressionValue.defaultExternalBindings + } -/* - Evaluates MathJs code via Reducer using bindings and answers the result. - When bindings are used, the code is a partial code as if it is cut from a larger code. - Therefore all statements are assignments. -*/ -let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) => { - parsePartial(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(bindings) - ) -} + let bindings = anExternalBindings->Bindings.fromExternalBindings -/* - Evaluates MathJs code via Reducer using bindings and answers the result. - When bindings are used, the code is a partial code as if it is cut from a larger code. - Therefore all statments are assignments. -*/ -let evalOuterWBindings_ = (codeText: string, bindings: T.bindings) => { - parseOuter(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(bindings) - ) + parse(code)->Result.flatMap(expr => evalUsingBindingsExpression_(expr, bindings, anEnvironment)) } /* Evaluates MathJs code and bindings via Reducer and answers the result */ -let eval = (codeText: string) => { - parse(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(defaultBindings) - ) -} - -type externalBindings = ReducerInterface.ExpressionValue.externalBindings //Js.Dict.t - -let externalBindingsToBindings = (externalBindings: externalBindings): T.bindings => { - let keys = Js.Dict.keys(externalBindings) - keys->Belt.Array.reduce(defaultBindings, (acc, key) => { - let value = Js.Dict.unsafeGet(externalBindings, key) - acc->Belt.Map.String.set(key, T.EValue(value)) - }) -} -/* - Evaluates code with external bindings. External bindings are a record of expression values. -*/ -let evalUsingExternalBindings = (code: string, externalBindings: externalBindings) => { - let bindings = externalBindings->externalBindingsToBindings - evalOuterWBindings_(code, bindings) -} - -/* - Evaluates code with external bindings. External bindings are a record of expression values. - The code is a partial code as if it is cut from a larger code. Therefore all statments are assignments. -*/ -let evalPartialUsingExternalBindings = (code: string, externalBindings: externalBindings): result< - externalBindings, - 'e, -> => { - let bindings = externalBindings->externalBindingsToBindings - let answer = evalPartialUsingExternalBindings_(code, bindings) - answer->Result.flatMap(answer => - switch answer { - | EvRecord(aRecord) => Ok(aRecord) - | _ => RETodo("TODO: External bindings must be returned")->Error - } +let evaluate = (code: string): result => { + evaluateUsingOptions(~environment=None, ~externalBindings=None, code) +} +let eval = evaluate +let evaluatePartialUsingExternalBindings = ( + code: string, + externalBindings: ReducerInterface_ExpressionValue.externalBindings, + environment: ReducerInterface_ExpressionValue.environment, +): result => { + let rAnswer = evaluateUsingOptions( + ~environment=Some(environment), + ~externalBindings=Some(externalBindings), + code, ) + switch rAnswer { + | Ok(EvRecord(externalBindings)) => Ok(externalBindings) + | Ok(_) => + Error(Reducer_ErrorValue.RESyntaxError(`Partials must end with an assignment or record`)) + | Error(err) => err->Error + } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_ExpressionWithContext.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_ExpressionWithContext.res new file mode 100644 index 00000000..dacd2462 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_ExpressionWithContext.res @@ -0,0 +1,45 @@ +module Bindings = Reducer_Expression_Bindings +module ErrorValue = Reducer_ErrorValue +module ExpressionT = Reducer_Expression_T +module ExpressionValue = ReducerInterface.ExpressionValue +module Result = Belt.Result + +type bindings = ExpressionT.bindings +type context = bindings +type environment = ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue +type expression = ExpressionT.expression +type expressionValue = ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings +type reducerFn = ExpressionT.reducerFn + +type expressionWithContext = + | ExpressionWithContext(expression, context) + | ExpressionNoContext(expression) + +let callReducer = ( + expressionWithContext: expressionWithContext, + bindings: bindings, + environment: environment, + reducer: reducerFn, +): result => + switch expressionWithContext { + | ExpressionNoContext(expr) => reducer(expr, bindings, environment) + | ExpressionWithContext(expr, context) => reducer(expr, context, environment) + } + +let withContext = (expression, context) => ExpressionWithContext(expression, context) +let noContext = expression => ExpressionNoContext(expression) + +let toString = expressionWithContext => + switch expressionWithContext { + | ExpressionNoContext(expr) => ExpressionT.toString(expr) + | ExpressionWithContext(expr, context) => + `${ExpressionT.toString(expr)} context: ${Bindings.toString(context)}` + } + +let toStringResult = rExpressionWithContext => + switch rExpressionWithContext { + | Ok(expressionWithContext) => `Ok(${toString(expressionWithContext)})` + | Error(errorValue) => ErrorValue.errorToString(errorValue) + } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Bindings.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Bindings.res new file mode 100644 index 00000000..7c0c048a --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Bindings.res @@ -0,0 +1,85 @@ +module ErrorValue = Reducer_ErrorValue +module ExpressionT = Reducer_Expression_T +module ExpressionValue = ReducerInterface.ExpressionValue +module Result = Belt.Result + +type errorValue = Reducer_ErrorValue.errorValue +type expression = ExpressionT.expression +type expressionValue = ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings + +let defaultBindings: ExpressionT.bindings = Belt.Map.String.empty + +let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.bindings => { + let keys = Js.Dict.keys(externalBindings) + keys->Belt.Array.reduce(defaultBindings, (acc, key) => { + let value = Js.Dict.unsafeGet(externalBindings, key) + acc->Belt.Map.String.set(key, value) + }) +} + +let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings => { + let keys = Belt.Map.String.keysToArray(bindings) + keys->Belt.Array.reduce(Js.Dict.empty(), (acc, key) => { + let value = bindings->Belt.Map.String.getExn(key) + Js.Dict.set(acc, key, value) + acc + }) +} + +let fromValue = (aValue: expressionValue) => + switch aValue { + | EvRecord(externalBindings) => fromExternalBindings(externalBindings) + | _ => defaultBindings + } + +let externalFromArray = anArray => Js.Dict.fromArray(anArray) + +let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$") + +let rec replaceSymbols = (bindings: ExpressionT.bindings, expression: expression): result< + expression, + errorValue, +> => + switch expression { + | ExpressionT.EValue(value) => + replaceSymbolOnValue(bindings, value)->Result.map(evValue => evValue->ExpressionT.EValue) + | ExpressionT.EList(list) => + switch list { + | list{EValue(EvCall(fName)), ..._args} => + switch isMacroName(fName) { + // A macro reduces itself so we dont dive in it + | true => expression->Ok + | false => replaceSymbolsOnExpressionList(bindings, list) + } + | _ => replaceSymbolsOnExpressionList(bindings, list) + } + } + +and replaceSymbolsOnExpressionList = (bindings, list) => { + let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) => + racc->Result.flatMap(acc => { + replaceSymbols(bindings, each)->Result.flatMap(newNode => { + acc->Belt.List.add(newNode)->Ok + }) + }) + ) + racc->Result.map(acc => acc->ExpressionT.EList) +} +and replaceSymbolOnValue = (bindings, evValue: expressionValue) => + switch evValue { + | EvSymbol(symbol) => Belt.Map.String.getWithDefault(bindings, symbol, evValue)->Ok + | EvCall(symbol) => Belt.Map.String.getWithDefault(bindings, symbol, evValue)->checkIfCallable + | _ => evValue->Ok + } +and checkIfCallable = (evValue: expressionValue) => + switch evValue { + | EvCall(_) | EvLambda(_) => evValue->Ok + | _ => ErrorValue.RENotAFunction(ExpressionValue.toString(evValue))->Error + } + +let toString = (bindings: ExpressionT.bindings) => + bindings->toExternalBindings->ExpressionValue.EvRecord->ExpressionValue.toString + +let externalBindingsToString = (externalBindings: externalBindings) => + externalBindings->ExpressionValue.EvRecord->ExpressionValue.toString diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res new file mode 100644 index 00000000..9c9f922e --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res @@ -0,0 +1,66 @@ +module BBindings = Reducer_Expression_Bindings +module BErrorValue = Reducer_ErrorValue +module BExpressionT = Reducer_Expression_T +module BExpressionValue = ReducerInterface.ExpressionValue + +type errorValue = BErrorValue.errorValue +type expression = BExpressionT.expression +type internalCode = ReducerInterface_ExpressionValue.internalCode + +external castExpressionToInternalCode: expression => internalCode = "%identity" + +let eArray = anArray => anArray->BExpressionValue.EvArray->BExpressionT.EValue + +let eArrayString = anArray => anArray->BExpressionValue.EvArrayString->BExpressionT.EValue + +let eBindings = (anArray: array<(string, BExpressionValue.expressionValue)>) => + anArray->Js.Dict.fromArray->EvRecord->BExpressionT.EValue + +let eBool = aBool => aBool->BExpressionValue.EvBool->BExpressionT.EValue + +let eCall = (name: string): expression => name->BExpressionValue.EvCall->BExpressionT.EValue + +let eFunction = (fName: string, lispArgs: list): expression => { + let fn = fName->eCall + list{fn, ...lispArgs}->BExpressionT.EList +} + +let eLambda = ( + parameters: array, + context: BExpressionValue.externalBindings, + expr: expression, +) => { + // Js.log(`eLambda context ${BBindings.externalBindingsToString(context)}`) + BExpressionValue.EvLambda({ + parameters: parameters, + context: context, + body: expr->castExpressionToInternalCode, + })->BExpressionT.EValue +} + +let eNumber = aNumber => aNumber->BExpressionValue.EvNumber->BExpressionT.EValue + +let eRecord = aRecord => aRecord->BExpressionValue.EvRecord->BExpressionT.EValue + +let eString = aString => aString->BExpressionValue.EvString->BExpressionT.EValue + +let eSymbol = (name: string): expression => name->BExpressionValue.EvSymbol->BExpressionT.EValue + +let eList = (list: list): expression => list->BExpressionT.EList + +let eBlock = (exprs: list): expression => eFunction("$$block", exprs) + +let eLetStatement = (symbol: string, valueExpression: expression): expression => + eFunction("$let", list{eSymbol(symbol), valueExpression}) + +let eBindStatement = (bindingExpr: expression, letStatement: expression): expression => + eFunction("$$bindStatement", list{bindingExpr, letStatement}) + +let eBindStatementDefault = (letStatement: expression): expression => + eFunction("$$bindStatement", list{letStatement}) + +let eBindExpression = (bindingExpr: expression, expression: expression): expression => + eFunction("$$bindExpression", list{bindingExpr, expression}) + +let eBindExpressionDefault = (expression: expression): expression => + eFunction("$$bindExpression", list{expression}) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res new file mode 100644 index 00000000..55931cc7 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res @@ -0,0 +1,60 @@ +module Bindings = Reducer_Expression_Bindings +module ErrorValue = Reducer_ErrorValue +module ExpressionBuilder = Reducer_Expression_ExpressionBuilder +module ExpressionT = Reducer_Expression_T +module ExpressionValue = ReducerInterface.ExpressionValue +module Result = Belt.Result + +type environment = ReducerInterface_ExpressionValue.environment +type expression = ExpressionT.expression +type expressionValue = ReducerInterface_ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings +type internalCode = ReducerInterface_ExpressionValue.internalCode + +external castInternalCodeToExpression: internalCode => expression = "%identity" + +let checkArity = (lambdaValue: ExpressionValue.lambdaValue, args: list) => { + let argsLength = Belt.List.length(args) + let parametersLength = Js.Array2.length(lambdaValue.parameters) + if argsLength !== parametersLength { + ErrorValue.REArityError(None, parametersLength, argsLength)->Error + } else { + args->Ok + } +} + +let checkIfReduced = (args: list) => + args->Belt.List.reduceReverse(Ok(list{}), (rAcc, arg) => + rAcc->Result.flatMap(acc => + switch arg { + | EvSymbol(symbol) => ErrorValue.RESymbolNotFound(symbol)->Error + | _ => list{arg, ...acc}->Ok + } + ) + ) + +let applyParametersToLambda = ( + lambdaValue: ExpressionValue.lambdaValue, + args, + environment, + reducer: ExpressionT.reducerFn, +): result => { + checkArity(lambdaValue, args)->Result.flatMap(args => + checkIfReduced(args)->Result.flatMap(args => { + let expr = castInternalCodeToExpression(lambdaValue.body) + let parameterList = lambdaValue.parameters->Belt.List.fromArray + let zippedParameterList = parameterList->Belt.List.zip(args) + let bindings = Belt.List.reduce( + zippedParameterList, + lambdaValue.context->Bindings.fromExternalBindings, + (acc, (variable, variableValue)) => acc->Belt.Map.String.set(variable, variableValue), + ) + let newExpression = ExpressionBuilder.eBlock(list{expr}) + reducer(newExpression, bindings, environment) + }) + ) +} + +let doLambdaCall = (lambdaValue: ExpressionValue.lambdaValue, args, environment, reducer) => { + applyParametersToLambda(lambdaValue, args, environment, reducer) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Macro.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Macro.res new file mode 100644 index 00000000..23fb70f8 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Macro.res @@ -0,0 +1,44 @@ +module ExpressionT = Reducer_Expression_T +module ExpressionValue = ReducerInterface.ExpressionValue +module ExpressionWithContext = Reducer_ExpressionWithContext +module Result = Belt.Result + +type environment = ExpressionValue.environment +type expression = ExpressionT.expression +type expressionValue = ExpressionValue.expressionValue +type expressionWithContext = ExpressionWithContext.expressionWithContext + +let expandMacroCall = ( + macroExpression: expression, + bindings: ExpressionT.bindings, + environment: environment, + reduceExpression: ExpressionT.reducerFn, +): result => + Reducer_Dispatch_BuiltInMacros.dispatchMacroCall( + macroExpression, + bindings, + environment, + reduceExpression, + ) + +let doMacroCall = ( + macroExpression: expression, + bindings: ExpressionT.bindings, + environment: environment, + reduceExpression: ExpressionT.reducerFn, +): result => + expandMacroCall( + macroExpression, + bindings, + environment, + reduceExpression, + )->Result.flatMap(expressionWithContext => + ExpressionWithContext.callReducer( + expressionWithContext, + bindings, + environment, + reduceExpression, + ) + ) + +let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$") diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index fe938316..b21ba8b7 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -1,5 +1,3 @@ -open ReducerInterface.ExpressionValue - /* An expression is a Lisp AST. An expression is either a primitive value or a list of expressions. In the case of a list of expressions (e1, e2, e3, ...eN), the semantic is @@ -8,8 +6,51 @@ open ReducerInterface.ExpressionValue A Lisp AST contains only expressions/primitive values to apply to their left. The act of defining the semantics of a functional language is to write it in terms of Lisp AST. */ +module Extra = Reducer_Extra +module ExpressionValue = ReducerInterface.ExpressionValue + +type expressionValue = ExpressionValue.expressionValue +type environment = ExpressionValue.environment + type rec expression = | EList(list) // A list to map-reduce | EValue(expressionValue) // Irreducible built-in value. Reducer should not know the internals. External libraries are responsible - | EBindings(bindings) // let/def kind of statements return bindings -and bindings = Belt.Map.String.t +and bindings = Belt.Map.String.t + +type reducerFn = ( + expression, + bindings, + environment, +) => result + +/* + Converts the expression to String +*/ +let rec toString = expression => + switch expression { + | EList(aList) => + `(${Belt.List.map(aList, aValue => toString(aValue)) + ->Extra.List.interperse(" ") + ->Belt.List.toArray + ->Js.String.concatMany("")})` + | EValue(aValue) => ExpressionValue.toString(aValue) + } + +let toStringResult = codeResult => + switch codeResult { + | Ok(a) => `Ok(${toString(a)})` + | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + } + +let inspect = (expr: expression): expression => { + Js.log(toString(expr)) + expr +} + +let inspectResult = (r: result): result< + expression, + Reducer_ErrorValue.errorValue, +> => { + Js.log(toStringResult(r)) + r +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Js/Reducer_Js_Gate.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Js/Reducer_Js_Gate.res index ad83edb1..7cd220bc 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Js/Reducer_Js_Gate.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Js/Reducer_Js_Gate.res @@ -8,11 +8,10 @@ external castString: unit => string = "%identity" /* As JavaScript returns us any type, we need to type check and cast type propertype before using it */ -let jsToEv = (jsValue): result => { +let jsToEv = (jsValue): result => switch Js.typeof(jsValue) { | "boolean" => jsValue->castBool->EvBool->Ok | "number" => jsValue->castNumber->EvNumber->Ok | "string" => jsValue->castString->EvString->Ok | other => RETodo(`Unhandled MathJs literal type: ${Js.String.make(other)}`)->Error } -} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_Parse.res index e3e2955c..1f5df97d 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_Parse.res @@ -11,11 +11,10 @@ type block = {"node": node} type blockNode = {...node, "blocks": array} //conditionalNode type constantNode = {...node, "value": unit} -//functionAssignmentNode +type functionAssignmentNode = {...node, "name": string, "params": array, "expr": node} type indexNode = {...node, "dimensions": array} type objectNode = {...node, "properties": Js.Dict.t} type accessorNode = {...node, "object": node, "index": indexNode, "name": string} - type parenthesisNode = {...node, "content": node} //rangeNode //relationalNode @@ -33,6 +32,7 @@ external castAssignmentNodeWAccessor: node => assignmentNodeWAccessor = "%identi external castAssignmentNodeWIndex: node => assignmentNodeWIndex = "%identity" external castBlockNode: node => blockNode = "%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" @@ -59,6 +59,7 @@ type mathJsNode = | MjAssignmentNode(assignmentNode) | MjBlockNode(blockNode) | MjConstantNode(constantNode) + | MjFunctionAssignmentNode(functionAssignmentNode) | MjFunctionNode(functionNode) | MjIndexNode(indexNode) | MjObjectNode(objectNode) @@ -82,6 +83,7 @@ let castNodeType = (node: node) => { | "AssignmentNode" => node->decideAssignmentNode | "BlockNode" => node->castBlockNode->MjBlockNode->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 @@ -118,6 +120,10 @@ let rec toString = (mathJsNode: mathJsNode): string => { ->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})` @@ -152,6 +158,7 @@ let rec toString = (mathJsNode: mathJsNode): string => { `${aNode["object"]->toStringSymbolNode} = ${aNode["value"]->toStringMathJsNode}` | MjBlockNode(bNode) => `{${bNode["blocks"]->toStringBlocks}}` | MjConstantNode(cNode) => cNode["value"]->toStringValue + | MjFunctionAssignmentNode(faNode) => faNode->toStringFunctionAssignmentNode | MjFunctionNode(fNode) => fNode->toStringFunctionNode | MjIndexNode(iNode) => iNode->toStringIndexNode | MjObjectNode(oNode) => oNode->toStringObjectNode diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_ToExpression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_ToExpression.res index a8b7b39a..ca040190 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_ToExpression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_MathJs/Reducer_MathJs_ToExpression.res @@ -1,45 +1,35 @@ +/* * 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 ExpressionValue = ReducerInterface.ExpressionValue +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 -type errorValue = ErrorValue.errorValue -let passToFunction = (fName: string, rLispArgs): result => { - let toEvCallValue = (name: string): expression => name->ExpressionValue.EvCall->ExpressionT.EValue +let blockToNode = block => block["node"] - let fn = fName->toEvCallValue - rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok) -} - -type blockTag = - | ImportVariablesStatement - | ExportVariablesExpression -type tagOrNode = - | BlockTag(blockTag) - | BlockNode(Parse.node) - -let toTagOrNode = block => BlockNode(block["node"]) - -let rec fromNode = (mathJsNode: Parse.node): result => +let rec fromInnerNode = (mathJsNode: Parse.node): result => Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => { let fromNodeList = (nodeList: list): result, 'e> => Belt.List.reduceReverse(nodeList, Ok(list{}), (racc, currNode) => racc->Result.flatMap(acc => - fromNode(currNode)->Result.map(currCode => list{currCode, ...acc}) + fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc}) ) ) - let toEvSymbolValue = (name: string): expression => - name->ExpressionValue.EvSymbol->ExpressionT.EValue - let caseFunctionNode = fNode => { - let lispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList - passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs) + let rLispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList + rLispArgs->Result.map(lispArgs => + ExpressionBuilder.eFunction(fNode->Parse.nameOfFunctionNode, lispArgs) + ) } let caseObjectNode = oNode => { @@ -49,19 +39,16 @@ let rec fromNode = (mathJsNode: Parse.node): result => (key: string, value: Parse.node), ) => racc->Result.flatMap(acc => - fromNode(value)->Result.map(valueExpression => { + fromInnerNode(value)->Result.map(valueExpression => { let entryCode = - list{ - key->ExpressionValue.EvString->ExpressionT.EValue, - valueExpression, - }->ExpressionT.EList + list{ExpressionBuilder.eString(key), valueExpression}->ExpressionT.EList list{entryCode, ...acc} }) ) ) rargs->Result.flatMap(args => - passToFunction("$constructRecord", list{ExpressionT.EList(args)}->Ok) - ) // $consturctRecord gets a single argument: List of key-value paiers + 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 @@ -73,7 +60,7 @@ let rec fromNode = (mathJsNode: Parse.node): result => Ok(list{}), (racc, currentPropertyMathJsNode) => racc->Result.flatMap(acc => - fromNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{ + fromInnerNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{ propertyCode, ...acc, }) @@ -84,18 +71,41 @@ let rec fromNode = (mathJsNode: Parse.node): result => let caseAccessorNode = (objectNode, indexNode) => { caseIndexNode(indexNode)->Result.flatMap(indexCode => { - fromNode(objectNode)->Result.flatMap(objectCode => - passToFunction("$atIndex", list{objectCode, indexCode}->Ok) + fromInnerNode(objectNode)->Result.flatMap(objectCode => + ExpressionBuilder.eFunction("$atIndex", list{objectCode, indexCode})->Ok ) }) } + let caseBlock = (nodesArray: array): result => { + let rStatements: result, '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 symbol = aNode["object"]["name"]->toEvSymbolValue - let rValueExpression = fromNode(aNode["value"]) + 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 lispArgs = list{symbol, valueExpression}->Ok - passToFunction("$let", lispArgs) + 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 }) } @@ -108,88 +118,22 @@ let rec fromNode = (mathJsNode: Parse.node): result => | MjArrayNode(aNode) => caseArrayNode(aNode) | MjAssignmentNode(aNode) => caseAssignmentNode(aNode) | MjSymbolNode(sNode) => { - let expr: expression = toEvSymbolValue(sNode["name"]) + let expr: expression = ExpressionBuilder.eSymbol(sNode["name"]) let rExpr: result = expr->Ok rExpr } - | MjBlockNode(bNode) => bNode["blocks"]->Belt.Array.map(toTagOrNode)->caseTagOrNodes + | MjBlockNode(bNode) => bNode["blocks"]->Js.Array2.map(blockToNode)->caseBlock | 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"]->fromNode + | MjParenthesisNode(pNode) => pNode["content"]->fromInnerNode } rFinalExpression }) -and caseTagOrNodes = (tagOrNodes): result => { - let initialBindings = passToFunction("$$bindings", list{}->Ok) - let lastIndex = Belt.Array.length(tagOrNodes) - 1 - tagOrNodes->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, tagOrNode, i) => { - rPreviousBindings->Result.flatMap(previousBindings => { - let rStatement: result = switch tagOrNode { - | BlockNode(node) => fromNode(node) - | BlockTag(tag) => - switch tag { - | ImportVariablesStatement => passToFunction("$importVariablesStatement", list{}->Ok) - | ExportVariablesExpression => passToFunction("$exportVariablesExpression", list{}->Ok) - } - } - let bindName = if i == lastIndex { - "$$bindExpression" - } else { - "$$bindStatement" - } - - rStatement->Result.flatMap((statement: expression) => { - let lispArgs = list{previousBindings, statement}->Ok - passToFunction(bindName, lispArgs) - }) - }) - }) -} - -let fromPartialNode = (mathJsNode: Parse.node): result => { - Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => { - let casePartialBlockNode = (bNode: Parse.blockNode) => { - let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode) - let completed = Js.Array2.concat(blocksOrTags, [BlockTag(ExportVariablesExpression)]) - completed->caseTagOrNodes - } - - let casePartialExpression = (node: Parse.node) => { - let completed = [BlockNode(node), BlockTag(ExportVariablesExpression)] - - completed->caseTagOrNodes - } - - let rFinalExpression: result = switch typedMathJsNode { - | MjBlockNode(bNode) => casePartialBlockNode(bNode) - | _ => casePartialExpression(mathJsNode) - } - rFinalExpression - }) -} - -let fromOuterNode = (mathJsNode: Parse.node): result => { - Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => { - let casePartialBlockNode = (bNode: Parse.blockNode) => { - let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode) - let completed = blocksOrTags - completed->caseTagOrNodes - } - - let casePartialExpression = (node: Parse.node) => { - let completed = [BlockNode(node)] - completed->caseTagOrNodes - } - - let rFinalExpression: result = switch typedMathJsNode { - | MjBlockNode(bNode) => casePartialBlockNode(bNode) - | _ => casePartialExpression(mathJsNode) - } - rFinalExpression - }) -} +let fromNode = (node: Parse.node): result => + fromInnerNode(node)->Result.map(expr => ExpressionBuilder.eBlock(list{expr})) diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index 381cc654..5c9ee4b7 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -5,37 +5,50 @@ module Extra_Array = Reducer_Extra_Array module ErrorValue = Reducer_ErrorValue +@genType.opaque +type internalCode = Object + @genType type rec expressionValue = | EvArray(array) + | EvArrayString(array) | EvBool(bool) | EvCall(string) // External function call | EvDistribution(DistributionTypes.genericDist) + | EvLambda(lambdaValue) | EvNumber(float) - | EvRecord(Js.Dict.t) + | EvRecord(record) | EvString(string) | EvSymbol(string) +and record = Js.Dict.t +and externalBindings = record +and lambdaValue = { + parameters: array, + context: externalBindings, + body: internalCode, +} @genType -type externalBindings = Js.Dict.t +let defaultExternalBindings: externalBindings = Js.Dict.empty() type functionCall = (string, array) let rec toString = aValue => switch aValue { + | EvArray(anArray) => { + let args = anArray->Js.Array2.map(each => toString(each))->Js.Array2.toString + `[${args}]` + } + | EvArrayString(anArray) => { + let args = anArray->Js.Array2.toString + `[${args}]` + } | EvBool(aBool) => Js.String.make(aBool) | EvCall(fName) => `:${fName}` + | EvLambda(lambdaValue) => `lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)` | EvNumber(aNumber) => Js.String.make(aNumber) | EvString(aString) => `'${aString}'` | EvSymbol(aString) => `:${aString}` - | EvArray(anArray) => { - let args = - anArray - ->Belt.Array.map(each => toString(each)) - ->Extra_Array.interperse(", ") - ->Js.String.concatMany("") - `[${args}]` - } | EvRecord(aRecord) => aRecord->toStringRecord | EvDistribution(dist) => GenericDist.toString(dist) } @@ -43,26 +56,27 @@ and toStringRecord = aRecord => { let pairs = aRecord ->Js.Dict.entries - ->Belt.Array.map(((eachKey, eachValue)) => `${eachKey}: ${toString(eachValue)}`) - ->Extra_Array.interperse(", ") - ->Js.String.concatMany("") + ->Js.Array2.map(((eachKey, eachValue)) => `${eachKey}: ${toString(eachValue)}`) + ->Js.Array2.toString `{${pairs}}` } let toStringWithType = aValue => switch aValue { + | EvArray(_) => `Array::${toString(aValue)}` + | EvArrayString(_) => `ArrayString::${toString(aValue)}` | EvBool(_) => `Bool::${toString(aValue)}` | EvCall(_) => `Call::${toString(aValue)}` + | EvDistribution(_) => `Distribution::${toString(aValue)}` + | EvLambda(_) => `Lambda::${toString(aValue)}` | EvNumber(_) => `Number::${toString(aValue)}` + | EvRecord(_) => `Record::${toString(aValue)}` | EvString(_) => `String::${toString(aValue)}` | EvSymbol(_) => `Symbol::${toString(aValue)}` - | EvArray(_) => `Array::${toString(aValue)}` - | EvRecord(_) => `Record::${toString(aValue)}` - | EvDistribution(_) => `Distribution::${toString(aValue)}` } let argsToString = (args: array): string => { - args->Belt.Array.map(arg => arg->toString)->Extra_Array.interperse(", ")->Js.String.concatMany("") + args->Js.Array2.map(arg => arg->toString)->Js.Array2.toString } let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)})` @@ -78,3 +92,9 @@ let toStringResultRecord = x => | Ok(a) => `Ok(${toStringRecord(a)})` | Error(m) => `Error(${ErrorValue.errorToString(m)})` } + +@genType +type environment = DistributionOperation.env + +@genType +let defaultEnvironment: environment = DistributionOperation.defaultEnv diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res index 84d37d95..0bdb0748 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res @@ -14,8 +14,13 @@ type expressionValue = ExpressionValue.expressionValue Map external calls of Reducer */ -let dispatch = (call: ExpressionValue.functionCall, chain): result => - ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(chain(call)) +let dispatch = (call: ExpressionValue.functionCall, environment, chain): result< + expressionValue, + 'e, +> => + ReducerInterface_GenericDistribution.dispatch(call, environment) |> E.O.default( + chain(call, environment), + ) /* If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res index 30585395..4615fa44 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res @@ -1,12 +1,12 @@ module ExpressionValue = ReducerInterface_ExpressionValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue -let runGenericOperation = DistributionOperation.run( - ~env={ - sampleCount: MagicNumbers.Environment.defaultSampleCount, - xyPointLength: MagicNumbers.Environment.defaultXYPointLength, - }, -) +let defaultEnv: DistributionOperation.env = { + sampleCount: MagicNumbers.Environment.defaultSampleCount, + xyPointLength: MagicNumbers.Environment.defaultXYPointLength, +} + +let runGenericOperation = DistributionOperation.run(~env=defaultEnv) module Helpers = { let arithmeticMap = r => @@ -28,14 +28,13 @@ module Helpers = { let catchAndConvertTwoArgsToDists = (args: array): option<( DistributionTypes.genericDist, DistributionTypes.genericDist, - )> => { + )> => switch args { | [EvDistribution(a), EvDistribution(b)] => Some((a, b)) | [EvNumber(a), EvDistribution(b)] => Some((GenericDist.fromFloat(a), b)) | [EvDistribution(a), EvNumber(b)] => Some((a, GenericDist.fromFloat(b))) | _ => None } - } let toFloatFn = ( fnCall: DistributionTypes.DistributionOperation.toFloat, @@ -119,7 +118,7 @@ module Helpers = { mixtureWithGivenWeights(distributions, weights) } - let mixture = (args: array): DistributionOperation.outputType => { + let mixture = (args: array): DistributionOperation.outputType => switch E.A.last(args) { | Some(EvArray(b)) => { let weights = parseNumberArray(b) @@ -139,7 +138,6 @@ module Helpers = { } | _ => GenDistError(ArgumentError("Last argument of mx must be array or distribution")) } - } } module SymbolicConstructors = { @@ -175,7 +173,7 @@ module SymbolicConstructors = { } } -let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option< +let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment): option< DistributionOperation.outputType, > => { let (fnName, args) = call @@ -294,6 +292,6 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result< | GenDistError(err) => Error(REDistributionError(err)) } -let dispatch = call => { - dispatchToGenericOutput(call)->E.O2.fmap(genericOutputToReducerValue) +let dispatch = (call, environment) => { + dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue) } diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi index fc7ebabc..038f4479 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi @@ -1,3 +1,5 @@ -let dispatch: ReducerInterface_ExpressionValue.functionCall => option< - result, -> +let defaultEnv: DistributionOperation.env +let dispatch: ( + ReducerInterface_ExpressionValue.functionCall, + ReducerInterface_ExpressionValue.environment, +) => option> diff --git a/packages/squiggle-lang/src/rescript/TypescriptInterface.res b/packages/squiggle-lang/src/rescript/TypescriptInterface.res index f2758ba3..6ebb8377 100644 --- a/packages/squiggle-lang/src/rescript/TypescriptInterface.res +++ b/packages/squiggle-lang/src/rescript/TypescriptInterface.res @@ -38,7 +38,7 @@ let makeSampleSetDist = SampleSetDist.make let evaluate = Reducer.evaluate @genType -let evaluateUsingExternalBindings = Reducer.evaluateUsingExternalBindings +let evaluateUsingOptions = Reducer.evaluateUsingOptions @genType let evaluatePartialUsingExternalBindings = Reducer.evaluatePartialUsingExternalBindings @@ -49,6 +49,9 @@ type externalBindings = Reducer.externalBindings @genType type expressionValue = ReducerInterface_ExpressionValue.expressionValue +@genType +type recordEV = ReducerInterface_ExpressionValue.record + @genType type errorValue = Reducer_ErrorValue.errorValue @@ -69,3 +72,15 @@ let errorValueToString = Reducer_ErrorValue.errorToString @genType let distributionErrorToString = DistributionTypes.Error.toString + +@genType +type lambdaValue = ReducerInterface_ExpressionValue.lambdaValue + +@genType +let defaultSamplingEnv = ReducerInterface_GenericDistribution.defaultEnv + +@genType +type environment = ReducerInterface_ExpressionValue.environment + +@genType +let defaultEnvironment = ReducerInterface_ExpressionValue.defaultEnvironment