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 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)
@ -17,7 +25,7 @@ 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), ~isPartial=None, ~environment=None)
->ExpressionValue.toStringResult
->expect
->toBe(answer)
@ -27,7 +35,8 @@ let expectEvalPartialBindingsToBe = (
bindings: Reducer.externalBindings,
answer: string,
) =>
Reducer.evaluatePartialUsingExternalBindings(expr, bindings)
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~isPartial=Some(true), ~environment=None)
->unwrapRecord
->ExpressionValue.toStringResultRecord
->expect
->toBe(answer)

View File

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

View File

@ -5,22 +5,29 @@ 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<expressionValue, Reducer_ErrorValue.errorValue>
@genType
let evaluateUsingExternalBindings: (
let evaluateUsingOptions: (
~environment: option<
QuriSquiggleLang.ReducerInterface_ExpressionValue.environment,
>,
~externalBindings: option<
QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings,
>,
~isPartial: option<bool>,
string,
externalBindings,
) => result<expressionValue, Reducer_ErrorValue.errorValue>
) => result<expressionValue, errorValue>
@genType
let evaluatePartialUsingExternalBindings: (
string,
externalBindings,
) => result<externalBindings, Reducer_ErrorValue.errorValue>
let parse: string => result<Expression.expression, ErrorValue.errorValue>
let parseOuter: string => result<Expression.expression, ErrorValue.errorValue>
let parsePartial: string => result<Expression.expression, ErrorValue.errorValue>
let evaluate: string => result<expressionValue, errorValue>
let parse: string => result<Expression.expression, errorValue>
let parseOuter: string => result<Expression.expression, errorValue>
let parsePartial: string => result<Expression.expression, errorValue>

View File

@ -11,7 +11,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
@ -85,12 +85,12 @@ let callInternal = (call: functionCall): result<'b, errorValue> => {
/*
Reducer uses Result monad while reducing expressions
*/
let dispatch = (call: functionCall): result<expressionValue, errorValue> =>
let dispatch = (call: functionCall, environment): result<expressionValue, errorValue> =>
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

View File

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

View File

@ -8,9 +8,12 @@ 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 internalCode = ExpressionValue.internalCode
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
type internalCode = ReducerInterface_ExpressionValue.internalCode
type t = expression
external castExpressionToInternalCode: expression => internalCode = "%identity"
@ -57,19 +60,20 @@ let defaultBindings: T.bindings = Belt.Map.String.empty
/*
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,
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<t>, bindings: T.bindings): result<t, 'e> =>
Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, reduceExpression)
let doMacroCall = (list: list<t>, bindings: T.bindings, environment: environment): result<t, 'e> =>
Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, environment, reduceExpression)
let applyParametersToLambda = (
internal: internalCode,
parameters: array<string>,
args: list<expressionValue>,
environment
): result<expressionValue, 'e> => {
let expr = castInternalCodeToExpression(internal)
let parameterList = parameters->Belt.List.fromArray
@ -81,22 +85,22 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
"$$bindExpression",
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
*/
let reduceValueList = (valueList: list<expressionValue>): result<expressionValue, 'e> =>
let reduceValueList = (valueList: list<expressionValue>, environment): result<expressionValue, 'e> =>
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)"
| list{EvLambda((parameters, internal)), ...args} =>
applyParametersToLambda(internal, parameters, args)
applyParametersToLambda(internal, parameters, args, environment)
| _ => 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 {
| T.EValue(_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 => {
each
->seekMacros(bindings)
->seekMacros(bindings, environment)
->Result.flatMap(newNode => {
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 {
| T.EList(list{T.EValue(EvCall("$lambda")), T.EParameters(parameters), functionDefinition}) =>
EvLambda((parameters, functionDefinition->castExpressionToInternalCode))->Ok
@ -130,36 +134,36 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
) =>
racc->Result.flatMap(acc => {
each
->reduceExpandedExpression
->reduceExpandedExpression(environment)
->Result.flatMap(newNode => {
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
| EParameters(_parameters) =>
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 =>
expandedExpression->reduceExpandedExpression
expandedExpression->reduceExpandedExpression(environment)
)
}
let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result<expressionValue, 'e> =>
reduceExpression(aExpression, bindings)
let evalUsingExternalBindingsExpression_ = (aExpression, bindings, environment): result<expressionValue, 'e> =>
reduceExpression(aExpression, bindings, environment)
/*
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) => {
let evalPartial_ = (codeText: string, bindings: T.bindings, environment: environment) => {
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.
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 =>
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 keys = Js.Dict.keys(externalBindings)
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))
})
}
/*
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)
let evaluateUsingOptions = (
~environment: option<ReducerInterface_ExpressionValue.environment>,
~externalBindings: option<ReducerInterface_ExpressionValue.externalBindings>,
~isPartial: option<bool>,
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.
The code is a partial code as if it is cut from a larger code. Therefore all statments are assignments.
Evaluates MathJs code and bindings via Reducer and answers the result
*/
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<expressionValue, errorValue> => {
evaluateUsingOptions(~environment=None, ~externalBindings=None, ~isPartial=None, code)
}
let eval = evaluate

View File

@ -22,6 +22,9 @@ type rec expressionValue =
@genType
type externalBindings = Js.Dict.t<expressionValue>
@genType
let defaultExternalBindings: externalBindings = Js.Dict.empty()
type functionCall = (string, array<expressionValue>)
@ -84,3 +87,8 @@ let toStringResultRecord = x =>
| Ok(a) => `Ok(${toStringRecord(a)})`
| 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
*/
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
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.

View File

@ -180,7 +180,7 @@ module Math = {
let e = 2.718281828459
}
let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment): option<
DistributionOperation.outputType,
> => {
let (fnName, args) = call
@ -266,6 +266,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)
}

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>,
>