Reducer: Environment (Give environement to all function dispatches) - closes #169
This commit is contained in:
parent
7d4e3072b8
commit
54f8b10a95
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,22 +5,29 @@ module Extra = Reducer_Extra
|
|||
module Js = Reducer_Js
|
||||
module MathJs = Reducer_MathJs
|
||||
|
||||
@genType
|
||||
@genType
|
||||
type environment = ReducerInterface_ExpressionValue.environment
|
||||
@genType
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
@genType
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
@genType
|
||||
@genType
|
||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||
@genType
|
||||
let evaluate: string => result<expressionValue, Reducer_ErrorValue.errorValue>
|
||||
@genType
|
||||
let evaluateUsingExternalBindings: (
|
||||
|
||||
@genType
|
||||
let evaluateUsingOptions: (
|
||||
~environment: option<
|
||||
QuriSquiggleLang.ReducerInterface_ExpressionValue.environment,
|
||||
>,
|
||||
~externalBindings: option<
|
||||
QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings,
|
||||
>,
|
||||
~isPartial: option<bool>,
|
||||
string,
|
||||
externalBindings,
|
||||
) => result<expressionValue, Reducer_ErrorValue.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>
|
||||
) => result<expressionValue, errorValue>
|
||||
@genType
|
||||
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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
>
|
||||
|
|
Loading…
Reference in New Issue
Block a user