module BuiltIn = Reducer_Dispatch_BuiltIn module ExpressionValue = ReducerInterface.ExpressionValue module Extra = Reducer_Extra module MathJs = Reducer_MathJs module Result = Belt.Result module T = Reducer_Expression_T open Reducer_ErrorValue type expression = T.expression type expressionValue = ExpressionValue.expressionValue 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 */ let parse_ = (expr: string, parser, converter): result => expr->parser->Result.flatMap(node => converter(node)) 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) /* 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 } 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)) } } 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 rExpandedExpression: result = expression->seekMacros(bindings) rExpandedExpression->Result.flatMap(expandedExpression => expandedExpression->reduceExpandedExpression ) } let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result => reduceExpression(aExpression, bindings) /* 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) ) } /* 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) ) } /* 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 } ) }