diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res index 78cb8a84..a1c78d8c 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_externalBindings_test.res @@ -14,6 +14,9 @@ describe("Parse for Bindings", () => { "y = x+1; y", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))", ) +}) + +describe("Parse for Bindings", () => { testParsePartialToBe( "x", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))", @@ -50,8 +53,12 @@ describe("Eval with Bindings", () => { ) }) +/* + Partial code is a partial code fragment that is cut out from a larger code. + Therefore it does not end with an expression. +*/ Only.describe("Eval Partial", () => { - MyOnly.testEvalPartialBindingsToBe( + testEvalPartialBindingsToBe( // A partial cannot end with an expression "x", list{("x", ExpressionValue.EvNumber(1.))}, @@ -60,17 +67,17 @@ Only.describe("Eval Partial", () => { testEvalPartialBindingsToBe( "y=x", list{("x", ExpressionValue.EvNumber(1.))}, - "????", + "Ok({x: 1, y: 1})", ) - testEvalBindingsToBe( + testEvalPartialBindingsToBe( "y=x+1", list{("x", ExpressionValue.EvNumber(1.))}, - "????", + "Ok({x: 1, y: 2})", ) - testEvalBindingsToBe( + MyOnly.testEvalPartialBindingsToBe( "y = x+1; z = y", list{("x", ExpressionValue.EvNumber(1.))}, - "????", + "Ok({x: 1, y: 2, z: 2})", ) }) 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 902e8f4e..793f0d02 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 @@ -1,14 +1,26 @@ +/* + 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. +*/ +module ExpressionT = Reducer_Expression_T module ExpressionValue = ReducerInterface.ExpressionValue module Result = Belt.Result -module ExpressionT = Reducer_Expression_T + open Reducer_ErrorValue type expression = ExpressionT.expression -let dispatchMacroCall = (list: list, bindings: ExpressionT.bindings): result< +type reducerFn = ( expression, - 'e, -> => { + ExpressionT.bindings, +) => result + +let dispatchMacroCall = ( + list: list, + bindings: ExpressionT.bindings, + reduceExpression: reducerFn, +): result => { let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result< expression, errorValue, @@ -40,9 +52,16 @@ let dispatchMacroCall = (list: list, bindings: ExpressionT.bindings) | ExpressionT.EList(list{ ExpressionT.EValue(EvCall("$let")), ExpressionT.EValue(EvSymbol(aSymbol)), - expression, + expressionToReduce, }) => { - let rNewExpression = replaceSymbols(expression, bindings) + let rNewExpressionToReduce = replaceSymbols(expressionToReduce, bindings) + + let rNewValue = + rNewExpressionToReduce->Result.flatMap(newExpressionToReduce => + reduceExpression(newExpressionToReduce, bindings) + ) + + let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue)) rNewExpression->Result.map(newExpression => Belt.Map.String.set(bindings, aSymbol, newExpression)->ExpressionT.EBindings ) @@ -51,35 +70,37 @@ let dispatchMacroCall = (list: list, bindings: ExpressionT.bindings) } } - let doBindExpression = (expression: expression, bindings: ExpressionT.bindings) => { + 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->EvRecord->ExpressionT.EValue->Ok + } + + 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) } - } - - 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->EvRecord->ExpressionT.EValue->Ok - } switch list { | list{ExpressionT.EValue(EvCall("$$bindings"))} => bindings->ExpressionT.EBindings->Ok @@ -90,11 +111,6 @@ let dispatchMacroCall = (list: list, bindings: ExpressionT.bindings) statement, } => doBindStatement(statement, bindings) - | list{ - ExpressionT.EValue(EvCall("$$bindExpression")), - ExpressionT.EBindings(bindings), - ExpressionT.EList(list{EValue(EvCall("$exportVariableExpression")), ..._}), - } => doExportVariableExpression(bindings) | list{ ExpressionT.EValue(EvCall("$$bindExpression")), ExpressionT.EBindings(bindings), 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 0b9ae9e2..c47ee191 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 @@ -50,7 +50,15 @@ let defaultBindings: T.bindings = Belt.Map.String.empty /* Recursively evaluate/reduce the expression (Lisp AST) */ -let reduceExpression = (expression: t, bindings: T.bindings): result => { +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) + /* After reducing each level of expression(Lisp AST), we have a value list to evaluate */ @@ -60,14 +68,6 @@ let reduceExpression = (expression: t, bindings: T.bindings): result 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 => - list->Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(bindings) - let rec seekMacros = (expression: t, bindings: T.bindings): result => switch expression { | T.EValue(_value) => expression->Ok