Reducer: Environment (Give environement to all function dispatches) - closes #169

This commit is contained in:
Umur Ozkul 2022-04-27 22:00:42 +02:00
parent 7d4e3072b8
commit 54f8b10a95
10 changed files with 119 additions and 85 deletions

View File

@ -1,9 +1,17 @@
module Expression = Reducer.Expression module Expression = Reducer.Expression
module ExpressionValue = ReducerInterface.ExpressionValue module ExpressionValue = ReducerInterface.ExpressionValue
module ErrorValue = Reducer_ErrorValue
open Jest open Jest
open Expect 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) => let expectParseToBe = (expr: string, answer: string) =>
Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer) Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer)
@ -17,7 +25,7 @@ let expectEvalToBe = (expr: string, answer: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
Reducer.evaluateUsingExternalBindings(expr, bindings) Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~isPartial=None, ~environment=None)
->ExpressionValue.toStringResult ->ExpressionValue.toStringResult
->expect ->expect
->toBe(answer) ->toBe(answer)
@ -27,7 +35,8 @@ let expectEvalPartialBindingsToBe = (
bindings: Reducer.externalBindings, bindings: Reducer.externalBindings,
answer: string, answer: string,
) => ) =>
Reducer.evaluatePartialUsingExternalBindings(expr, bindings) Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~isPartial=Some(true), ~environment=None)
->unwrapRecord
->ExpressionValue.toStringResultRecord ->ExpressionValue.toStringResultRecord
->expect ->expect
->toBe(answer) ->toBe(answer)

View File

@ -5,11 +5,12 @@ module Extra = Reducer_Extra
module Js = Reducer_Js module Js = Reducer_Js
module MathJs = Reducer_MathJs module MathJs = Reducer_MathJs
type environment = ReducerInterface_ExpressionValue.environment
type errorValue = Reducer_ErrorValue.errorValue
type expressionValue = ReducerInterface_ExpressionValue.expressionValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue
type externalBindings = ReducerInterface_ExpressionValue.externalBindings type externalBindings = ReducerInterface_ExpressionValue.externalBindings
let evaluate = Expression.eval let evaluate = Expression.evaluate
let evaluateUsingExternalBindings = Expression.evalUsingExternalBindings let evaluateUsingOptions = Expression.evaluateUsingOptions
let evaluatePartialUsingExternalBindings = Expression.evalPartialUsingExternalBindings
let parse = Expression.parse let parse = Expression.parse
let parseOuter = Expression.parseOuter let parseOuter = Expression.parseOuter
let parsePartial = Expression.parsePartial let parsePartial = Expression.parsePartial

View File

@ -5,22 +5,29 @@ module Extra = Reducer_Extra
module Js = Reducer_Js module Js = Reducer_Js
module MathJs = Reducer_MathJs module MathJs = Reducer_MathJs
@genType
type environment = ReducerInterface_ExpressionValue.environment
@genType
type errorValue = Reducer_ErrorValue.errorValue
@genType @genType
type expressionValue = ReducerInterface_ExpressionValue.expressionValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue
@genType @genType
type externalBindings = ReducerInterface_ExpressionValue.externalBindings type externalBindings = ReducerInterface_ExpressionValue.externalBindings
@genType @genType
let evaluate: string => result<expressionValue, Reducer_ErrorValue.errorValue> let evaluateUsingOptions: (
@genType ~environment: option<
let evaluateUsingExternalBindings: ( QuriSquiggleLang.ReducerInterface_ExpressionValue.environment,
>,
~externalBindings: option<
QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings,
>,
~isPartial: option<bool>,
string, string,
externalBindings, ) => result<expressionValue, errorValue>
) => result<expressionValue, Reducer_ErrorValue.errorValue>
@genType @genType
let evaluatePartialUsingExternalBindings: ( let evaluate: string => result<expressionValue, errorValue>
string,
externalBindings, let parse: string => result<Expression.expression, errorValue>
) => result<externalBindings, Reducer_ErrorValue.errorValue> let parseOuter: string => result<Expression.expression, errorValue>
let parse: string => result<Expression.expression, ErrorValue.errorValue> let parsePartial: string => result<Expression.expression, errorValue>
let parseOuter: string => result<Expression.expression, ErrorValue.errorValue>
let parsePartial: string => result<Expression.expression, ErrorValue.errorValue>

View File

@ -11,7 +11,7 @@ open Reducer_ErrorValue
exception TestRescriptException exception TestRescriptException
let callInternal = (call: functionCall): result<'b, errorValue> => { let callInternal = (call: functionCall, _environment): result<'b, errorValue> => {
let callMathJs = (call: functionCall): result<'b, errorValue> => let callMathJs = (call: functionCall): result<'b, errorValue> =>
switch call { switch call {
| ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests | ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests
@ -85,12 +85,12 @@ let callInternal = (call: functionCall): result<'b, errorValue> => {
/* /*
Reducer uses Result monad while reducing expressions Reducer uses Result monad while reducing expressions
*/ */
let dispatch = (call: functionCall): result<expressionValue, errorValue> => let dispatch = (call: functionCall, environment): result<expressionValue, errorValue> =>
try { try {
let (fn, args) = call let (fn, args) = call
// There is a bug that prevents string match in patterns // There is a bug that prevents string match in patterns
// So we have to recreate a copy of the string // 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 { } catch {
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
| _ => RETodo("unhandled rescript exception")->Error | _ => RETodo("unhandled rescript exception")->Error

View File

@ -10,10 +10,12 @@ module Result = Belt.Result
open Reducer_ErrorValue open Reducer_ErrorValue
type expression = ExpressionT.expression type expression = ExpressionT.expression
type environment = ExpressionValue.environment
type reducerFn = ( type reducerFn = (
expression, expression,
ExpressionT.bindings, ExpressionT.bindings,
environment,
) => result<ExpressionValue.expressionValue, errorValue> ) => result<ExpressionValue.expressionValue, errorValue>
let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result< let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result<
@ -101,6 +103,7 @@ let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings
let dispatchMacroCall = ( let dispatchMacroCall = (
list: list<expression>, list: list<expression>,
bindings: ExpressionT.bindings, bindings: ExpressionT.bindings,
environment,
reduceExpression: reducerFn, reduceExpression: reducerFn,
): result<expression, 'e> => { ): result<expression, 'e> => {
let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => { let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => {
@ -114,7 +117,7 @@ let dispatchMacroCall = (
let rNewValue = let rNewValue =
rNewExpressionToReduce->Result.flatMap(newExpressionToReduce => rNewExpressionToReduce->Result.flatMap(newExpressionToReduce =>
reduceExpression(newExpressionToReduce, bindings) reduceExpression(newExpressionToReduce, bindings, environment)
) )
let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue)) let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue))

View File

@ -8,9 +8,12 @@ module T = Reducer_Expression_T
open Reducer_ErrorValue open Reducer_ErrorValue
type environment = ReducerInterface_ExpressionValue.environment
type errorValue = Reducer_ErrorValue.errorValue
type expression = T.expression type expression = T.expression
type expressionValue = ExpressionValue.expressionValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue
type internalCode = ExpressionValue.internalCode type externalBindings = ReducerInterface_ExpressionValue.externalBindings
type internalCode = ReducerInterface_ExpressionValue.internalCode
type t = expression type t = expression
external castExpressionToInternalCode: expression => internalCode = "%identity" external castExpressionToInternalCode: expression => internalCode = "%identity"
@ -57,19 +60,20 @@ let defaultBindings: T.bindings = Belt.Map.String.empty
/* /*
Recursively evaluate/reduce the expression (Lisp AST) Recursively evaluate/reduce the expression (Lisp AST)
*/ */
let rec reduceExpression = (expression: t, bindings: T.bindings): result<expressionValue, 'e> => { let rec reduceExpression = (expression: t, bindings: T.bindings, environment: environment): result<expressionValue, 'e> => {
/* /*
Macros are like functions but instead of taking values as parameters, Macros are like functions but instead of taking values as parameters,
they take expressions as parameters and return a new expression. they take expressions as parameters and return a new expression.
Macros are used to define language building blocks. They are like Lisp macros. Macros are used to define language building blocks. They are like Lisp macros.
*/ */
let doMacroCall = (list: list<t>, bindings: T.bindings): result<t, 'e> => let doMacroCall = (list: list<t>, bindings: T.bindings, environment: environment): result<t, 'e> =>
Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, reduceExpression) Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, environment, reduceExpression)
let applyParametersToLambda = ( let applyParametersToLambda = (
internal: internalCode, internal: internalCode,
parameters: array<string>, parameters: array<string>,
args: list<expressionValue>, args: list<expressionValue>,
environment
): result<expressionValue, 'e> => { ): result<expressionValue, 'e> => {
let expr = castInternalCodeToExpression(internal) let expr = castInternalCodeToExpression(internal)
let parameterList = parameters->Belt.List.fromArray let parameterList = parameters->Belt.List.fromArray
@ -81,22 +85,22 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
"$$bindExpression", "$$bindExpression",
list{Builder.passToFunction("$$bindings", list{}), expr}, list{Builder.passToFunction("$$bindings", list{}), expr},
) )
reduceExpression(newExpression, bindings) reduceExpression(newExpression, bindings, environment)
} }
/* /*
After reducing each level of expression(Lisp AST), we have a value list to evaluate After reducing each level of expression(Lisp AST), we have a value list to evaluate
*/ */
let reduceValueList = (valueList: list<expressionValue>): result<expressionValue, 'e> => let reduceValueList = (valueList: list<expressionValue>, environment): result<expressionValue, 'e> =>
switch valueList { switch valueList {
| list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch | list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment)
// "(lambda(x=>internal) param)" // "(lambda(x=>internal) param)"
| list{EvLambda((parameters, internal)), ...args} => | list{EvLambda((parameters, internal)), ...args} =>
applyParametersToLambda(internal, parameters, args) applyParametersToLambda(internal, parameters, args, environment)
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok | _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
} }
let rec seekMacros = (expression: t, bindings: T.bindings): result<t, 'e> => let rec seekMacros = (expression: t, bindings: T.bindings, environment): result<t, 'e> =>
switch expression { switch expression {
| T.EValue(_value) => expression->Ok | T.EValue(_value) => expression->Ok
| T.EBindings(_value) => expression->Ok | T.EBindings(_value) => expression->Ok
@ -108,17 +112,17 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
) => ) =>
racc->Result.flatMap(acc => { racc->Result.flatMap(acc => {
each each
->seekMacros(bindings) ->seekMacros(bindings, environment)
->Result.flatMap(newNode => { ->Result.flatMap(newNode => {
acc->Belt.List.add(newNode)->Ok acc->Belt.List.add(newNode)->Ok
}) })
}) })
) )
racc->Result.flatMap(acc => acc->doMacroCall(bindings)) racc->Result.flatMap(acc => acc->doMacroCall(bindings, environment))
} }
} }
let rec reduceExpandedExpression = (expression: t): result<expressionValue, 'e> => let rec reduceExpandedExpression = (expression: t, environment): result<expressionValue, 'e> =>
switch expression { switch expression {
| T.EList(list{T.EValue(EvCall("$lambda")), T.EParameters(parameters), functionDefinition}) => | T.EList(list{T.EValue(EvCall("$lambda")), T.EParameters(parameters), functionDefinition}) =>
EvLambda((parameters, functionDefinition->castExpressionToInternalCode))->Ok EvLambda((parameters, functionDefinition->castExpressionToInternalCode))->Ok
@ -130,36 +134,36 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
) => ) =>
racc->Result.flatMap(acc => { racc->Result.flatMap(acc => {
each each
->reduceExpandedExpression ->reduceExpandedExpression(environment)
->Result.flatMap(newNode => { ->Result.flatMap(newNode => {
acc->Belt.List.add(newNode)->Ok acc->Belt.List.add(newNode)->Ok
}) })
}) })
) )
racc->Result.flatMap(acc => acc->reduceValueList) racc->Result.flatMap(acc => acc->reduceValueList(environment))
} }
| EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error | EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error
| EParameters(_parameters) => | EParameters(_parameters) =>
RETodo("Error: Lambda Parameters cannot be reduced to values")->Error RETodo("Error: Lambda Parameters cannot be reduced to values")->Error
} }
let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings) let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings, environment)
rExpandedExpression->Result.flatMap(expandedExpression => rExpandedExpression->Result.flatMap(expandedExpression =>
expandedExpression->reduceExpandedExpression expandedExpression->reduceExpandedExpression(environment)
) )
} }
let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result<expressionValue, 'e> => let evalUsingExternalBindingsExpression_ = (aExpression, bindings, environment): result<expressionValue, 'e> =>
reduceExpression(aExpression, bindings) reduceExpression(aExpression, bindings, environment)
/* /*
Evaluates MathJs code via Reducer using bindings and answers the result. 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. When bindings are used, the code is a partial code as if it is cut from a larger code.
Therefore all statements are assignments. Therefore all statements are assignments.
*/ */
let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) => { let evalPartial_ = (codeText: string, bindings: T.bindings, environment: environment) => {
parsePartial(codeText)->Result.flatMap(expression => parsePartial(codeText)->Result.flatMap(expression =>
expression->evalUsingExternalBindingsExpression_(bindings) expression->evalUsingExternalBindingsExpression_(bindings, environment)
) )
} }
@ -168,23 +172,12 @@ let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings)
When bindings are used, the code is a partial code as if it is cut from a larger code. When bindings are used, the code is a partial code as if it is cut from a larger code.
Therefore all statments are assignments. Therefore all statments are assignments.
*/ */
let evalOuterWBindings_ = (codeText: string, bindings: T.bindings) => { let evalOuter_ = (codeText: string, bindings: T.bindings, environment: environment) => {
parseOuter(codeText)->Result.flatMap(expression => parseOuter(codeText)->Result.flatMap(expression =>
expression->evalUsingExternalBindingsExpression_(bindings) expression->evalUsingExternalBindingsExpression_(bindings, environment)
) )
} }
/*
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<expressionValue>
let externalBindingsToBindings = (externalBindings: externalBindings): T.bindings => { let externalBindingsToBindings = (externalBindings: externalBindings): T.bindings => {
let keys = Js.Dict.keys(externalBindings) let keys = Js.Dict.keys(externalBindings)
keys->Belt.Array.reduce(defaultBindings, (acc, key) => { keys->Belt.Array.reduce(defaultBindings, (acc, key) => {
@ -192,28 +185,41 @@ let externalBindingsToBindings = (externalBindings: externalBindings): T.binding
acc->Belt.Map.String.set(key, T.EValue(value)) acc->Belt.Map.String.set(key, T.EValue(value))
}) })
} }
/*
Evaluates code with external bindings. External bindings are a record of expression values. let evaluateUsingOptions = (
*/ ~environment: option<ReducerInterface_ExpressionValue.environment>,
let evalUsingExternalBindings = (code: string, externalBindings: externalBindings) => { ~externalBindings: option<ReducerInterface_ExpressionValue.externalBindings>,
let bindings = externalBindings->externalBindingsToBindings ~isPartial: option<bool>,
evalOuterWBindings_(code, bindings) code: string): result<expressionValue, errorValue> => {
let anEnvironment = switch environment {
| Some(env) => env
| None => ReducerInterface_ExpressionValue.defaultEnvironment
}
let anExternalBindings = switch externalBindings {
| Some(bindings) => bindings
| None => ReducerInterface_ExpressionValue.defaultExternalBindings
}
let anIsPartial = switch isPartial {
| Some(isPartial) => isPartial
| None => false
}
let bindings = anExternalBindings->externalBindingsToBindings
if anIsPartial {
evalPartial_(code, bindings, anEnvironment)
} else {
evalOuter_(code, bindings, anEnvironment)
}
} }
/* /*
Evaluates code with external bindings. External bindings are a record of expression values. Evaluates MathJs code and bindings via Reducer and answers the result
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< let evaluate = (code: string): result<expressionValue, errorValue> => {
externalBindings, evaluateUsingOptions(~environment=None, ~externalBindings=None, ~isPartial=None, code)
'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 eval = evaluate

View File

@ -22,6 +22,9 @@ type rec expressionValue =
@genType @genType
type externalBindings = Js.Dict.t<expressionValue> type externalBindings = Js.Dict.t<expressionValue>
@genType
let defaultExternalBindings: externalBindings = Js.Dict.empty()
type functionCall = (string, array<expressionValue>) type functionCall = (string, array<expressionValue>)
@ -84,3 +87,8 @@ let toStringResultRecord = x =>
| Ok(a) => `Ok(${toStringRecord(a)})` | Ok(a) => `Ok(${toStringRecord(a)})`
| Error(m) => `Error(${ErrorValue.errorToString(m)})` | Error(m) => `Error(${ErrorValue.errorToString(m)})`
} }
@genType
type environment = {dummy: int}
@genType
let defaultEnvironment: environment = {dummy: 0}

View File

@ -14,8 +14,8 @@ type expressionValue = ExpressionValue.expressionValue
Map external calls of Reducer Map external calls of Reducer
*/ */
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> => let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<expressionValue, 'e> =>
ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(chain(call)) 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. If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.

View File

@ -180,7 +180,7 @@ module Math = {
let e = 2.718281828459 let e = 2.718281828459
} }
let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option< let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment): option<
DistributionOperation.outputType, DistributionOperation.outputType,
> => { > => {
let (fnName, args) = call let (fnName, args) = call
@ -266,6 +266,6 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
| GenDistError(err) => Error(REDistributionError(err)) | GenDistError(err) => Error(REDistributionError(err))
} }
let dispatch = call => { let dispatch = (call, environment) => {
dispatchToGenericOutput(call)->E.O2.fmap(genericOutputToReducerValue) dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue)
} }

View File

@ -1,3 +1,3 @@
let dispatch: ReducerInterface_ExpressionValue.functionCall => option< let dispatch: (ReducerInterface_ExpressionValue.functionCall, ReducerInterface_ExpressionValue.environment) => option<
result<ReducerInterface_ExpressionValue.expressionValue, Reducer_ErrorValue.errorValue>, result<ReducerInterface_ExpressionValue.expressionValue, Reducer_ErrorValue.errorValue>,
> >