diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn_test.res index 2fd2a976..d8a6748c 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn_test.res @@ -6,15 +6,18 @@ open Expect let expectEvalToBe = (expr: string, answer: string) => Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer) +let testEval = (expr, answer) => + test(expr, () => expectEvalToBe(expr, answer)) + describe("builtin", () => { // All MathJs operators and functions are available for string, number and boolean // .e.g + - / * > >= < <= == /= not and or // See https://mathjs.org/docs/expressions/syntax.html // See https://mathjs.org/docs/reference/functions.html - test("-1", () => expectEvalToBe("-1", "Ok(-1)")) - test("1-1", () => expectEvalToBe("1-1", "Ok(0)")) - test("2>1", () => expectEvalToBe("2>1", "Ok(true)")) - test("concat('a','b')", () => expectEvalToBe("concat('a','b')", "Ok('ab')")) + testEval("-1", "Ok(-1)") + testEval("1-1", "Ok(0)") + testEval("2>1", "Ok(true)") + testEval("concat('a','b')", "Ok('ab')") }) describe("builtin exception", () => { 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 4ec86785..fce588f1 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 @@ -11,9 +11,11 @@ let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer) let testDescParse = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer)) -let skipTestParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) +module MySkip = { + let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) -let skipDescTestParse = (desc, expr, answer) => Skip.test(desc, () => expectParseToBe(expr, answer)) + let testDescParse = (desc, expr, answer) => Skip.test(desc, () => expectParseToBe(expr, answer)) +} describe("MathJs parse", () => { describe("literals operators paranthesis", () => { @@ -26,6 +28,10 @@ describe("MathJs parse", () => { testParse("(1+2)", "(add(1, 2))") }) + describe("multi-line", () => { + testParse("1; 2", "{1; 2}") + }) + describe("variables", () => { testParse("x = 1", "x = 1") testParse("x", "x") @@ -33,16 +39,16 @@ describe("MathJs parse", () => { }) describe("functions", () => { - skipTestParse("identity(x) = x", "???") - skipTestParse("identity(x)", "???") + MySkip.testParse("identity(x) = x", "???") + MySkip.testParse("identity(x)", "???") }) describe("arrays", () => { - test("empty", () => expectParseToBe("[]", "[]")) - test("define", () => expectParseToBe("[0, 1, 2]", "[0, 1, 2]")) - test("define with strings", () => expectParseToBe("['hello', 'world']", "['hello', 'world']")) - skipTestParse("range(0, 4)", "range(0, 4)") - test("index", () => expectParseToBe("([0,1,2])[1]", "([0, 1, 2])[1]")) + testDescParse("empty", "[]", "[]") + testDescParse("define", "[0, 1, 2]", "[0, 1, 2]") + testDescParse("define with strings", "['hello', 'world']", "['hello', 'world']") + MySkip.testParse("range(0, 4)", "range(0, 4)") + testDescParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]") }) describe("records", () => { @@ -51,10 +57,10 @@ describe("MathJs parse", () => { }) describe("comments", () => { - skipDescTestParse("define", "# This is a comment", "???") + MySkip.testDescParse("define", "# This is a comment", "???") }) - describe("if statement", () => { - skipDescTestParse("define", "if (true) { 1 } else { 0 }", "???") + describe("if statement", () => { // TODO Tertiary operator instead + MySkip.testDescParse("define", "if (true) { 1 } else { 0 }", "???") }) }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index a7539b7f..035ba7d9 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -42,6 +42,15 @@ describe("reducer using mathjs parse", () => { "Ok((:$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)))") + }) + 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)))") + }) + }) describe("eval", () => { @@ -71,13 +80,26 @@ describe("eval", () => { test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)")) test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)")) }) + + describe("multi-line", () => { + testEvalToBe("1; 2", "Error(Assignment expected)") + testEvalToBe("1+1; 2+1", "Error(Assignment expected)") + }) + describe("assignment", () => { + testEvalToBe("x=1; x", "Ok(1)") + testEvalToBe("x=1+1; x+1", "Ok(3)") + testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") + testEvalToBe("1; x=1", "Error(Assignment expected)") + testEvalToBe("1; 1", "Error(Assignment expected)") + testEvalToBe("x=1; x=1", "Error(Expression expected)") + }) }) describe("test exceptions", () => { testDescEvalToBe( "javascript exception", - "jsraise('div by 0')", + "javascriptraise('div by 0')", "Error(JS Exception: Error: 'div by 0')", ) - testDescEvalToBe("rescript exception", "resraise()", "Error(TODO: unhandled rescript exception)") + testDescEvalToBe("rescript exception", "rescriptraise()", "Error(TODO: unhandled rescript exception)") }) diff --git a/packages/squiggle-lang/package.json b/packages/squiggle-lang/package.json index a968a156..6c920067 100644 --- a/packages/squiggle-lang/package.json +++ b/packages/squiggle-lang/package.json @@ -12,6 +12,7 @@ "test:watch": "jest --watchAll", "coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html", "rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;", + "reducer:format": "find src/rescript/Reducer src/rescript/ReducerInterface -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;", "all": "yarn build && yarn bundle && yarn test" }, "keywords": [ 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 824e36ef..01ce67cb 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 @@ -14,8 +14,8 @@ exception TestRescriptException let callInternal = (call: functionCall): result<'b, errorValue> => { let callMathJs = (call: functionCall): result<'b, errorValue> => switch call { - | ("jsraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests - | ("resraise", _) => raise(TestRescriptException) // For Tests + | ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests + | ("rescriptraise", _) => raise(TestRescriptException) // For Tests | call => call->toStringFunctionCall->MathJs.Eval.eval } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index 70601726..0e23bc99 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -1,8 +1,12 @@ type errorValue = | REArrayIndexNotFound(string, int) + | REAssignmentExpected + | REExpressionExpected | REFunctionExpected(string) | REJavaScriptExn(option, option) // Javascript Exception + | REMacroNotFound(string) | RERecordPropertyNotFound(string, string) + | RESymbolNotFound(string) | RESyntaxError(string) | RETodo(string) // To do @@ -11,6 +15,8 @@ type t = errorValue let errorToString = err => switch err { | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` + | REAssignmentExpected => "Assignment expected" + | REExpressionExpected => "Expression expected" | REFunctionExpected(msg) => `Function expected: ${msg}` | REJavaScriptExn(omsg, oname) => { let answer = "JS Exception:" @@ -24,7 +30,9 @@ let errorToString = err => } answer } + | REMacroNotFound(macro) => `Macro not found: ${macro}` | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` + | RESymbolNotFound(symbolName) => `${symbolName} is not defined` | RESyntaxError(desc) => `Syntax Error: ${desc}` | RETodo(msg) => `TODO: ${msg}` } 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 76e75e07..6aeaa81d 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 @@ -15,6 +15,7 @@ type t = expression */ let rec toString = expression => switch expression { + | T.EBindings(bindings) => "$$bound" | T.EList(aList) => `(${Belt.List.map(aList, aValue => toString(aValue)) ->Extra.List.interperse(" ") @@ -38,50 +39,137 @@ let parse_ = (expr: string, parser, converter): result => let parse = (mathJsCode: string): result => mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode) -module MapString = Belt.Map.String -type bindings = MapString.t -let defaultBindings: bindings = MapString.fromArray([]) -// TODO Define bindings for function execution context +let defaultBindings: T.bindings = Belt.Map.String.empty /* - After reducing each level of code tree, we have a value list to evaluate + Recursively evaluate/reduce the expression (Lisp AST) */ -let reduceValueList = (valueList: list): result => - switch valueList { - | list{EvSymbol(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch - | _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok - } - -/* - Recursively evaluate/reduce the code tree -*/ -let rec reduceExpression = (expression: t, bindings): 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 - ->reduceExpression(bindings) - ->Result.flatMap(newNode => { - acc->Belt.List.add(newNode)->Ok - }) - }) - ) - racc->Result.flatMap(acc => acc->reduceValueList) +let rec reduceExpression = (expression: t, bindings: T.bindings): result => { + /* + 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 } + + /* + 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 => { + let dispatchMacroCall = (list: list, bindings: T.bindings): result => { + let rec replaceSymbols = (expression: t, bindings: T.bindings): result => + switch expression { + | T.EValue(EvSymbol(aSymbol)) => + switch bindings->Belt.Map.String.get(aSymbol) { + | Some(boundExpression) => boundExpression->Ok + | None => RESymbolNotFound(aSymbol)->Error + } + | T.EValue(_) => expression->Ok + | T.EBindings(_) => expression->Ok + | T.EList(list) => + 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 + }) + }) + ) + ->Result.map(acc => acc->T.EList) + } + + let doBindStatement = (statement: t, bindings: T.bindings) => { + switch statement { + | T.EList(list{T.EValue(EvCall("$let")), T.EValue(EvSymbol(aSymbol)), expression}) => { + let rNewExpression = replaceSymbols(expression, bindings) + rNewExpression->Result.map(newExpression => + Belt.Map.String.set(bindings, aSymbol, newExpression)->T.EBindings + ) + } + | _ => REAssignmentExpected->Error + } + } + + let doBindExpression = (expression: t, bindings: T.bindings) => { + switch expression { + | T.EList(list{T.EValue(EvCall("$let")), ..._}) => REExpressionExpected->Error + | _ => replaceSymbols(expression, bindings) + } + } + + switch list { + | list{T.EValue(EvCall("$$bindings"))} => bindings->EBindings->Ok + + | list{T.EValue(EvCall("$$bindStatement")), T.EBindings(bindings), statement} => + doBindStatement(statement, bindings) + | list{T.EValue(EvCall("$$bindExpression")), T.EBindings(bindings), expression} => + doBindExpression(expression, bindings) + | _ => list->T.EList->Ok + } + } + + list->dispatchMacroCall(bindings) } + let rec seekMacros = (expression: t, bindings: T.bindings): result => + switch expression { + | T.EValue(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)) + } + } + + 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) + } + } + + let rExpandedExpression: result = expression->seekMacros(bindings) + rExpandedExpression->Result.flatMap(expandedExpression => + expandedExpression->reduceExpandedExpression + ) +} + let evalWBindingsExpression = (aExpression, bindings): result => reduceExpression(aExpression, bindings) /* Evaluates MathJs code via Lisp using bindings and answers the result */ -let evalWBindings = (codeText: string, bindings: bindings) => { +let evalWBindings = (codeText: string, bindings: T.bindings) => { parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings)) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.resi b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.resi deleted file mode 100644 index afd0105f..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.resi +++ /dev/null @@ -1,16 +0,0 @@ -module Result = Belt.Result -module T = Reducer_Expression_T -type expression = T.expression -type expressionValue = ReducerInterface.ExpressionValue.expressionValue -type t = expression -let toString: T.expression => Js.String.t -let toStringResult: result => string -let parse: string => result -module MapString = Belt.Map.String -type bindings = MapString.t -let defaultBindings: bindings -let reduceValueList: list => result -let reduceExpression: (expression, 'a) => result -let evalWBindingsExpression: (expression, 'a) => result -let evalWBindings: (string, bindings) => Result.t -let eval: string => Result.t 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 5f376050..fe938316 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,15 @@ 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 + apply e1, e2 -> apply e3 -> ... -> apply eN + This is Lisp semantics. It holds true in both eager and lazy evaluations. + 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. +*/ 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 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 7f20bacf..e3e2955c 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 @@ -12,17 +12,16 @@ type blockNode = {...node, "blocks": array} //conditionalNode type constantNode = {...node, "value": unit} //functionAssignmentNode -type functionNode = {...node, "fn": string, "args": array} type indexNode = {...node, "dimensions": array} type objectNode = {...node, "properties": Js.Dict.t} -type accessorNode = {...node, "object": node, "index": indexNode} -type operatorNode = {...functionNode, "op": string} +type accessorNode = {...node, "object": node, "index": indexNode, "name": string} -//parenthesisNode type parenthesisNode = {...node, "content": node} //rangeNode //relationalNode type symbolNode = {...node, "name": string} +type functionNode = {...node, "fn": unit, "args": array} +type operatorNode = {...functionNode, "op": string} type assignmentNode = {...node, "object": symbolNode, "value": node} type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node} type assignmentNodeWIndex = {...assignmentNodeWAccessor, "index": Js.null} @@ -93,6 +92,18 @@ let castNodeType = (node: node) => { } } +external unitAsSymbolNode: unit => symbolNode = "%identity" +external unitAsString: unit => string = "%identity" + +let nameOfFunctionNode = (fNode: functionNode): string => { + let name = fNode["fn"] + if Js.typeof(name) == "string" { + name->unitAsString + } else { + (name->unitAsSymbolNode)["name"] + } +} + let rec toString = (mathJsNode: mathJsNode): string => { let toStringValue = (a: 'a): string => if Js.typeof(a) == "string" { @@ -108,7 +119,7 @@ let rec toString = (mathJsNode: mathJsNode): string => { ->Js.String.concatMany("") let toStringFunctionNode = (fnode: functionNode): string => - `${fnode["fn"]}(${fnode["args"]->toStringNodeArray})` + `${fnode->nameOfFunctionNode}(${fnode["args"]->toStringNodeArray})` let toStringObjectEntry = ((key: string, value: node)): string => `${key}: ${value->toStringMathJsNode}` 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 edd0313b..30c8651d 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,11 +1,11 @@ module ErrorValue = Reducer_ErrorValue module ExpressionValue = ReducerInterface.ExpressionValue -module ExtressionT = Reducer_Expression_T +module ExpressionT = Reducer_Expression_T module JavaScript = Reducer_Js module Parse = Reducer_MathJs_Parse module Result = Belt.Result -type expression = ExtressionT.expression +type expression = ExpressionT.expression type expressionValue = ExpressionValue.expressionValue type errorValue = ErrorValue.errorValue @@ -18,10 +18,19 @@ let rec fromNode = (mathJsNode: Parse.node): result => ) ) - let castFunctionNode = fNode => { - let fn = fNode["fn"]->ExpressionValue.EvSymbol->ExtressionT.EValue + let toEvCallValue = (name: string): expression => + name->ExpressionValue.EvCall->ExpressionT.EValue + let toEvSymbolValue = (name: string): expression => + name->ExpressionValue.EvSymbol->ExpressionT.EValue + + let passToFunction = (fName: string, rLispArgs): result => { + let fn = fName->toEvCallValue + rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok) + } + + let caseFunctionNode = fNode => { let lispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList - lispArgs->Result.map(argsCode => list{fn, ...argsCode}->ExtressionT.EList) + passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs) } let caseObjectNode = oNode => { @@ -34,15 +43,16 @@ let rec fromNode = (mathJsNode: Parse.node): result => fromNode(value)->Result.map(valueExpression => { let entryCode = list{ - key->ExpressionValue.EvString->ExtressionT.EValue, + key->ExpressionValue.EvString->ExpressionT.EValue, valueExpression, - }->ExtressionT.EList + }->ExpressionT.EList list{entryCode, ...acc} }) ) ) - let lispName = "$constructRecord"->ExpressionValue.EvSymbol->ExtressionT.EValue - rargs->Result.map(args => list{lispName, ExtressionT.EList(args)}->ExtressionT.EList) + rargs->Result.flatMap(args => + passToFunction("$constructRecord", list{ExpressionT.EList(args)}->Ok) + ) // $consturctRecord gets a single argument: List of key-value paiers } oNode["properties"]->Js.Dict.entries->Belt.List.fromArray->fromObjectEntries @@ -60,30 +70,69 @@ let rec fromNode = (mathJsNode: Parse.node): result => }) ), ) - rpropertyCodeList->Result.map(propertyCodeList => ExtressionT.EList(propertyCodeList)) + rpropertyCodeList->Result.map(propertyCodeList => ExpressionT.EList(propertyCodeList)) } let caseAccessorNode = (objectNode, indexNode) => { - let fn = "$atIndex"->ExpressionValue.EvSymbol->ExtressionT.EValue - caseIndexNode(indexNode)->Result.flatMap(indexCode => { - fromNode(objectNode)->Result.map(objectCode => - list{fn, objectCode, indexCode}->ExtressionT.EList + fromNode(objectNode)->Result.flatMap(objectCode => + passToFunction("$atIndex", list{objectCode, indexCode}->Ok) ) }) } - switch typedMathJsNode { - | MjArrayNode(aNode) => - aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExtressionT.EList(list)) - | MjConstantNode(cNode) => - cNode["value"]->JavaScript.Gate.jsToEv->Result.map(v => v->ExtressionT.EValue) - | MjFunctionNode(fNode) => fNode->castFunctionNode - | MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->castFunctionNode - | MjParenthesisNode(pNode) => pNode["content"]->fromNode - | MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"]) - | MjObjectNode(oNode) => caseObjectNode(oNode) - | MjSymbolNode(sNode) => sNode["name"]->ExpressionValue.EvSymbol->ExtressionT.EValue->Ok - | MjIndexNode(iNode) => caseIndexNode(iNode) + let caseAssignmentNode = aNode => { + let symbol = aNode["object"]["name"]->toEvSymbolValue + let rValueExpression = fromNode(aNode["value"]) + rValueExpression->Result.flatMap(valueExpression => { + let lispArgs = list{symbol, valueExpression}->Ok + passToFunction("$let", lispArgs) + }) } + + let caseArrayNode = aNode => { + aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list)) + } + + let caseBlockNode = (bNode): result => { + let blocks = bNode["blocks"] + let initialBindings = passToFunction("$$bindings", list{}->Ok) + let lastIndex = Belt.Array.length(blocks) - 1 + blocks->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, block, i) => { + rPreviousBindings->Result.flatMap(previousBindings => { + let node = block["node"] + let rStatement: result = node->fromNode + let bindName = if i == lastIndex { + "$$bindExpression" + } else { + "$$bindStatement" + } + rStatement->Result.flatMap((statement: expression) => { + let lispArgs = list{previousBindings, statement}->Ok + passToFunction(bindName, lispArgs) + }) + }) + }) + } + + let rFinalExpression: result = switch typedMathJsNode { + | MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"]) + | MjArrayNode(aNode) => caseArrayNode(aNode) + | MjAssignmentNode(aNode) => caseAssignmentNode(aNode) + | MjSymbolNode(sNode) => { + let expr: expression = toEvSymbolValue(sNode["name"]) + let rExpr: result = expr->Ok + rExpr + } + | MjBlockNode(bNode) => caseBlockNode(bNode) + // | MjBlockNode(bNode) => "statement"->toEvSymbolValue->Ok + | MjConstantNode(cNode) => + cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok) + | MjFunctionNode(fNode) => fNode->caseFunctionNode + | MjIndexNode(iNode) => caseIndexNode(iNode) + | MjObjectNode(oNode) => caseObjectNode(oNode) + | MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->caseFunctionNode + | MjParenthesisNode(pNode) => pNode["content"]->fromNode + } + rFinalExpression }) diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index 10ac33aa..064f2db3 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -6,19 +6,21 @@ module Extra_Array = Reducer_Extra_Array module ErrorValue = Reducer_ErrorValue type rec expressionValue = + | EvArray(array) | EvBool(bool) + | EvCall(string) // External function call + | EvDistribution(GenericDist_Types.genericDist) | EvNumber(float) + | EvRecord(Js.Dict.t) | EvString(string) | EvSymbol(string) - | EvArray(array) - | EvRecord(Js.Dict.t) - | EvDistribution(GenericDist_Types.genericDist) type functionCall = (string, array) let rec toString = aValue => switch aValue { | EvBool(aBool) => Js.String.make(aBool) + | EvCall(fName) => `:${fName}` | EvNumber(aNumber) => Js.String.make(aNumber) | EvString(aString) => `'${aString}'` | EvSymbol(aString) => `:${aString}` @@ -39,12 +41,13 @@ let rec toString = aValue => ->Js.String.concatMany("") `{${pairs}}` } - | EvDistribution(dist) => `${GenericDist.toString(dist)}` + | EvDistribution(dist) => GenericDist.toString(dist) } let toStringWithType = aValue => switch aValue { | EvBool(_) => `Bool::${toString(aValue)}` + | EvCall(_) => `Call::${toString(aValue)}` | EvNumber(_) => `Number::${toString(aValue)}` | EvString(_) => `String::${toString(aValue)}` | EvSymbol(_) => `Symbol::${toString(aValue)}`