Merge pull request #333 from quantified-uncertainty/reducer-dev
Bindings in and Out
This commit is contained in:
commit
d6e4f77624
|
@ -7,5 +7,66 @@ open Expect
|
|||
let expectParseToBe = (expr: string, answer: string) =>
|
||||
Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectParseOuterToBe = (expr: string, answer: string) =>
|
||||
Reducer.parseOuter(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectParsePartialToBe = (expr: string, answer: string) =>
|
||||
Reducer.parsePartial(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
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)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectEvalPartialBindingsToBe = (
|
||||
expr: string,
|
||||
bindings: Reducer.externalBindings,
|
||||
answer: string,
|
||||
) =>
|
||||
Reducer.evaluatePartialUsingExternalBindings(expr, bindings)
|
||||
->ExpressionValue.toStringResultRecord
|
||||
->expect
|
||||
->toBe(answer)
|
||||
|
||||
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) => test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) => test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||
test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
test(expr, () => expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
|
||||
module MySkip = {
|
||||
let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) =>
|
||||
Skip.test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) =>
|
||||
Skip.test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Skip.test(expr, () =>
|
||||
expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)
|
||||
)
|
||||
}
|
||||
module MyOnly = {
|
||||
let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) =>
|
||||
Only.test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) =>
|
||||
Only.test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Only.test(expr, () =>
|
||||
expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
describe("Parse for Bindings", () => {
|
||||
testParseOuterToBe("x", "Ok((:$$bindExpression (:$$bindings) :x))")
|
||||
testParseOuterToBe("x+1", "Ok((:$$bindExpression (:$$bindings) (:add :x 1)))")
|
||||
testParseOuterToBe(
|
||||
"y = x+1; y",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Parse Partial", () => {
|
||||
testParsePartialToBe(
|
||||
"x",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y=x",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y :x)) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y=x+1",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y = x+1; z = y",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$let :z :y)) (:$exportVariablesExpression)))",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Eval with Bindings", () => {
|
||||
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
||||
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||
})
|
||||
|
||||
/*
|
||||
Partial code is a partial code fragment that is cut out from a larger code.
|
||||
Therefore it does not end with an expression.
|
||||
*/
|
||||
describe("Eval Partial", () => {
|
||||
testEvalPartialBindingsToBe(
|
||||
// A partial cannot end with an expression
|
||||
"x",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Error(Assignment expected)",
|
||||
)
|
||||
testEvalPartialBindingsToBe("y=x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1, y: 1})")
|
||||
testEvalPartialBindingsToBe(
|
||||
"y=x+1",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Ok({x: 1, y: 2})",
|
||||
)
|
||||
testEvalPartialBindingsToBe(
|
||||
"y = x+1; z = y",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Ok({x: 1, y: 2, z: 2})",
|
||||
)
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
Skip.describe("Parse ternary operator", () => {
|
||||
testParseToBe("true ? 'YES' : 'NO'", "Ok('YES')")
|
||||
testParseToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
})
|
||||
|
||||
Skip.describe("Evaluate ternary operator", () => {
|
||||
testEvalToBe("true ? 'YES' : 'NO'", "Ok('YES')")
|
||||
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
})
|
|
@ -1,15 +1,6 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||
test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||
|
||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||
|
||||
describe("reducer using mathjs parse", () => {
|
||||
// Test the MathJs parser compatibility
|
||||
// Those tests toString that there is a semantic mapping from MathJs to Expression
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
|
||||
"test:watch": "jest --watchAll",
|
||||
"coverage:rescript": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test:rescript; bisect-ppx-report html",
|
||||
"coverage:ts": "nyc --reporter=lcov yarn test:ts",
|
||||
"coverage:ts": "yarn clean; yarn build; nyc --reporter=lcov yarn test:ts",
|
||||
"coverage:rescript:ci": "yarn clean; BISECT_ENABLE=yes yarn build; yarn test:rescript; bisect-ppx-report send-to Codecov",
|
||||
"coverage:ts:ci": "yarn coverage:ts && codecov",
|
||||
"lint:rescript": "./lint.sh",
|
||||
|
@ -48,6 +48,7 @@
|
|||
"gentype": "^4.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"moduleserve": "0.9.1",
|
||||
"reanalyze": "^2.19.0",
|
||||
"nyc": "^15.1.0",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-loader": "^9.2.8",
|
||||
|
|
|
@ -6,5 +6,10 @@ module Js = Reducer_Js
|
|||
module MathJs = Reducer_MathJs
|
||||
|
||||
type expressionValue = Reducer_Expression.expressionValue
|
||||
type externalBindings = Expression.externalBindings
|
||||
let evaluate = Expression.eval
|
||||
let evaluateUsingExternalBindings = Expression.evalUsingExternalBindings
|
||||
let evaluatePartialUsingExternalBindings = Expression.evalPartialUsingExternalBindings
|
||||
let parse = Expression.parse
|
||||
let parseOuter = Expression.parseOuter
|
||||
let parsePartial = Expression.parsePartial
|
||||
|
|
|
@ -7,7 +7,20 @@ module MathJs = Reducer_MathJs
|
|||
|
||||
@genType
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
@genType
|
||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||
@genType
|
||||
let evaluate: string => result<expressionValue, Reducer_ErrorValue.errorValue>
|
||||
@genType
|
||||
let evaluateUsingExternalBindings: (
|
||||
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>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
module Builtin = Reducer_Dispatch_BuiltIn
|
||||
module BuiltinMacros = Reducer_Dispatch_BuiltInMacros
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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
|
||||
|
||||
open Reducer_ErrorValue
|
||||
|
||||
type expression = ExpressionT.expression
|
||||
|
||||
type reducerFn = (
|
||||
expression,
|
||||
ExpressionT.bindings,
|
||||
) => result<ExpressionValue.expressionValue, errorValue>
|
||||
|
||||
let dispatchMacroCall = (
|
||||
list: list<expression>,
|
||||
bindings: ExpressionT.bindings,
|
||||
reduceExpression: reducerFn,
|
||||
): result<expression, 'e> => {
|
||||
let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result<
|
||||
expression,
|
||||
errorValue,
|
||||
> =>
|
||||
switch expression {
|
||||
| ExpressionT.EValue(EvSymbol(aSymbol)) =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => RESymbolNotFound(aSymbol)->Error
|
||||
}
|
||||
| ExpressionT.EValue(_) => expression->Ok
|
||||
| ExpressionT.EBindings(_) => expression->Ok
|
||||
| ExpressionT.EList(list) => {
|
||||
let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->replaceSymbols(bindings)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.map(acc => acc->ExpressionT.EList)
|
||||
}
|
||||
}
|
||||
|
||||
let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => {
|
||||
switch statement {
|
||||
| ExpressionT.EList(list{
|
||||
ExpressionT.EValue(EvCall("$let")),
|
||||
ExpressionT.EValue(EvSymbol(aSymbol)),
|
||||
expressionToReduce,
|
||||
}) => {
|
||||
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
|
||||
)
|
||||
}
|
||||
| _ => REAssignmentExpected->Error
|
||||
}
|
||||
}
|
||||
|
||||
let doExportVariableExpression = (bindings: ExpressionT.bindings) => {
|
||||
let emptyDictionary: Js.Dict.t<ExpressionValue.expressionValue> = 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)
|
||||
}
|
||||
|
||||
switch list {
|
||||
| list{ExpressionT.EValue(EvCall("$$bindings"))} => bindings->ExpressionT.EBindings->Ok
|
||||
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindStatement")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
statement,
|
||||
} =>
|
||||
doBindStatement(statement, bindings)
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindExpression")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
expression,
|
||||
} =>
|
||||
doBindExpression(expression, bindings)
|
||||
| _ => list->ExpressionT.EList->Ok
|
||||
}
|
||||
}
|
|
@ -39,12 +39,26 @@ let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
|||
let parse = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
||||
|
||||
let parsePartial = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromPartialNode)
|
||||
|
||||
let parseOuter = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromOuterNode)
|
||||
|
||||
let defaultBindings: T.bindings = Belt.Map.String.empty
|
||||
|
||||
/*
|
||||
Recursively evaluate/reduce the expression (Lisp AST)
|
||||
*/
|
||||
let reduceExpression = (expression: t, bindings: T.bindings): result<expressionValue, 'e> => {
|
||||
let rec reduceExpression = (expression: t, bindings: T.bindings): 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)
|
||||
|
||||
/*
|
||||
After reducing each level of expression(Lisp AST), we have a value list to evaluate
|
||||
*/
|
||||
|
@ -54,72 +68,10 @@ let reduceExpression = (expression: t, bindings: T.bindings): result<expressionV
|
|||
| _ => 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<t>, bindings: T.bindings): result<t, 'e> => {
|
||||
let dispatchMacroCall = (list: list<t>, bindings: T.bindings): result<t, 'e> => {
|
||||
let rec replaceSymbols = (expression: t, bindings: T.bindings): result<t, errorValue> =>
|
||||
switch expression {
|
||||
| T.EValue(EvSymbol(aSymbol)) =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => RESymbolNotFound(aSymbol)->Error
|
||||
}
|
||||
| T.EValue(_) => expression->Ok
|
||||
| T.EBindings(_) => expression->Ok
|
||||
| T.EList(list) => {
|
||||
let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->replaceSymbols(bindings)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.map(acc => acc->T.EList)
|
||||
}
|
||||
}
|
||||
|
||||
let doBindStatement = (statement: t, bindings: T.bindings) => {
|
||||
switch statement {
|
||||
| T.EList(list{T.EValue(EvCall("$let")), T.EValue(EvSymbol(aSymbol)), expression}) => {
|
||||
let rNewExpression = replaceSymbols(expression, bindings)
|
||||
rNewExpression->Result.map(newExpression =>
|
||||
Belt.Map.String.set(bindings, aSymbol, newExpression)->T.EBindings
|
||||
)
|
||||
}
|
||||
| _ => REAssignmentExpected->Error
|
||||
}
|
||||
}
|
||||
|
||||
let doBindExpression = (expression: t, bindings: T.bindings) => {
|
||||
switch expression {
|
||||
| T.EList(list{T.EValue(EvCall("$let")), ..._}) => REExpressionExpected->Error
|
||||
| _ => replaceSymbols(expression, bindings)
|
||||
}
|
||||
}
|
||||
|
||||
switch list {
|
||||
| list{T.EValue(EvCall("$$bindings"))} => bindings->T.EBindings->Ok
|
||||
|
||||
| list{T.EValue(EvCall("$$bindStatement")), T.EBindings(bindings), statement} =>
|
||||
doBindStatement(statement, bindings)
|
||||
| list{T.EValue(EvCall("$$bindExpression")), T.EBindings(bindings), expression} =>
|
||||
doBindExpression(expression, bindings)
|
||||
| _ => list->T.EList->Ok
|
||||
}
|
||||
}
|
||||
|
||||
list->dispatchMacroCall(bindings)
|
||||
}
|
||||
|
||||
let rec seekMacros = (expression: t, bindings: T.bindings): result<t, 'e> =>
|
||||
switch expression {
|
||||
| T.EValue(_) => expression->Ok
|
||||
| T.EValue(_value) => expression->Ok
|
||||
| T.EBindings(_value) => expression->Ok
|
||||
| T.EList(list) => {
|
||||
let racc: result<list<t>, 'e> = list->Belt.List.reduceReverse(Ok(list{}), (
|
||||
racc,
|
||||
|
@ -135,7 +87,6 @@ let reduceExpression = (expression: t, bindings: T.bindings): result<expressionV
|
|||
)
|
||||
racc->Result.flatMap(acc => acc->doMacroCall(bindings))
|
||||
}
|
||||
| T.EBindings(bindings) => T.EBindings(bindings)->Ok
|
||||
}
|
||||
|
||||
let rec reduceExpandedExpression = (expression: t): result<expressionValue, 'e> =>
|
||||
|
@ -156,7 +107,7 @@ let reduceExpression = (expression: t, bindings: T.bindings): result<expressionV
|
|||
)
|
||||
racc->Result.flatMap(acc => acc->reduceValueList)
|
||||
}
|
||||
| T.EBindings(_) => RETodo("Cannot return bindings")->Error
|
||||
| EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error
|
||||
}
|
||||
|
||||
let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings)
|
||||
|
@ -165,17 +116,69 @@ let reduceExpression = (expression: t, bindings: T.bindings): result<expressionV
|
|||
)
|
||||
}
|
||||
|
||||
let evalWBindingsExpression = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||
let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||
reduceExpression(aExpression, bindings)
|
||||
|
||||
/*
|
||||
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.
|
||||
Therefore all statements are assignments.
|
||||
*/
|
||||
let evalWBindings = (codeText: string, bindings: T.bindings) => {
|
||||
parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings))
|
||||
let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) => {
|
||||
parsePartial(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(bindings)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Evaluates MathJs code via Reducer 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.
|
||||
Therefore all statments are assignments.
|
||||
*/
|
||||
let eval = (code: string) => evalWBindings(code, defaultBindings)
|
||||
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<expressionValue>
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,22 @@ type expression = ExpressionT.expression
|
|||
type expressionValue = ExpressionValue.expressionValue
|
||||
type errorValue = ErrorValue.errorValue
|
||||
|
||||
let passToFunction = (fName: string, rLispArgs): result<expression, errorValue> => {
|
||||
let toEvCallValue = (name: string): expression => name->ExpressionValue.EvCall->ExpressionT.EValue
|
||||
|
||||
let fn = fName->toEvCallValue
|
||||
rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok)
|
||||
}
|
||||
|
||||
type blockTag =
|
||||
| ImportVariablesStatement
|
||||
| ExportVariablesExpression
|
||||
type tagOrNode =
|
||||
| BlockTag(blockTag)
|
||||
| BlockNode(Parse.node)
|
||||
|
||||
let toTagOrNode = block => BlockNode(block["node"])
|
||||
|
||||
let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
||||
|
@ -18,16 +34,9 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
)
|
||||
)
|
||||
|
||||
let toEvCallValue = (name: string): expression =>
|
||||
name->ExpressionValue.EvCall->ExpressionT.EValue
|
||||
let toEvSymbolValue = (name: string): expression =>
|
||||
name->ExpressionValue.EvSymbol->ExpressionT.EValue
|
||||
|
||||
let passToFunction = (fName: string, rLispArgs): result<expression, errorValue> => {
|
||||
let fn = fName->toEvCallValue
|
||||
rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok)
|
||||
}
|
||||
|
||||
let caseFunctionNode = fNode => {
|
||||
let lispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
||||
passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
||||
|
@ -94,27 +103,6 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list))
|
||||
}
|
||||
|
||||
let caseBlockNode = (bNode): result<expression, errorValue> => {
|
||||
let blocks = bNode["blocks"]
|
||||
let initialBindings = passToFunction("$$bindings", list{}->Ok)
|
||||
let lastIndex = Belt.Array.length(blocks) - 1
|
||||
blocks->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, block, i) => {
|
||||
rPreviousBindings->Result.flatMap(previousBindings => {
|
||||
let node = block["node"]
|
||||
let rStatement: result<expression, errorValue> = node->fromNode
|
||||
let bindName = if i == lastIndex {
|
||||
"$$bindExpression"
|
||||
} else {
|
||||
"$$bindStatement"
|
||||
}
|
||||
rStatement->Result.flatMap((statement: expression) => {
|
||||
let lispArgs = list{previousBindings, statement}->Ok
|
||||
passToFunction(bindName, lispArgs)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
||||
| MjArrayNode(aNode) => caseArrayNode(aNode)
|
||||
|
@ -124,8 +112,7 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
let rExpr: result<expression, errorValue> = expr->Ok
|
||||
rExpr
|
||||
}
|
||||
| MjBlockNode(bNode) => caseBlockNode(bNode)
|
||||
// | MjBlockNode(bNode) => "statement"->toEvSymbolValue->Ok
|
||||
| MjBlockNode(bNode) => bNode["blocks"]->Belt.Array.map(toTagOrNode)->caseTagOrNodes
|
||||
| MjConstantNode(cNode) =>
|
||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
||||
| MjFunctionNode(fNode) => fNode->caseFunctionNode
|
||||
|
@ -136,3 +123,73 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
}
|
||||
rFinalExpression
|
||||
})
|
||||
and caseTagOrNodes = (tagOrNodes): result<expression, errorValue> => {
|
||||
let initialBindings = passToFunction("$$bindings", list{}->Ok)
|
||||
let lastIndex = Belt.Array.length(tagOrNodes) - 1
|
||||
tagOrNodes->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, tagOrNode, i) => {
|
||||
rPreviousBindings->Result.flatMap(previousBindings => {
|
||||
let rStatement: result<expression, errorValue> = switch tagOrNode {
|
||||
| BlockNode(node) => fromNode(node)
|
||||
| BlockTag(tag) =>
|
||||
switch tag {
|
||||
| ImportVariablesStatement => passToFunction("$importVariablesStatement", list{}->Ok)
|
||||
| ExportVariablesExpression => passToFunction("$exportVariablesExpression", list{}->Ok)
|
||||
}
|
||||
}
|
||||
|
||||
let bindName = if i == lastIndex {
|
||||
"$$bindExpression"
|
||||
} else {
|
||||
"$$bindStatement"
|
||||
}
|
||||
|
||||
rStatement->Result.flatMap((statement: expression) => {
|
||||
let lispArgs = list{previousBindings, statement}->Ok
|
||||
passToFunction(bindName, lispArgs)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let fromPartialNode = (mathJsNode: Parse.node): result<expression, errorValue> => {
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let casePartialBlockNode = (bNode: Parse.blockNode) => {
|
||||
let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode)
|
||||
let completed = Js.Array2.concat(blocksOrTags, [BlockTag(ExportVariablesExpression)])
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let casePartialExpression = (node: Parse.node) => {
|
||||
let completed = [BlockNode(node), BlockTag(ExportVariablesExpression)]
|
||||
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjBlockNode(bNode) => casePartialBlockNode(bNode)
|
||||
| _ => casePartialExpression(mathJsNode)
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
}
|
||||
|
||||
let fromOuterNode = (mathJsNode: Parse.node): result<expression, errorValue> => {
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let casePartialBlockNode = (bNode: Parse.blockNode) => {
|
||||
let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode)
|
||||
let completed = blocksOrTags
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let casePartialExpression = (node: Parse.node) => {
|
||||
let completed = [BlockNode(node)]
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjBlockNode(bNode) => casePartialBlockNode(bNode)
|
||||
| _ => casePartialExpression(mathJsNode)
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ type rec expressionValue =
|
|||
| EvString(string)
|
||||
| EvSymbol(string)
|
||||
|
||||
@genType
|
||||
type externalBindings = Js.Dict.t<expressionValue>
|
||||
|
||||
type functionCall = (string, array<expressionValue>)
|
||||
|
||||
let rec toString = aValue =>
|
||||
|
@ -33,7 +36,10 @@ let rec toString = aValue =>
|
|||
->Js.String.concatMany("")
|
||||
`[${args}]`
|
||||
}
|
||||
| EvRecord(aRecord) => {
|
||||
| EvRecord(aRecord) => aRecord->toStringRecord
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
and toStringRecord = aRecord => {
|
||||
let pairs =
|
||||
aRecord
|
||||
->Js.Dict.entries
|
||||
|
@ -42,8 +48,6 @@ let rec toString = aValue =>
|
|||
->Js.String.concatMany("")
|
||||
`{${pairs}}`
|
||||
}
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
|
||||
let toStringWithType = aValue =>
|
||||
switch aValue {
|
||||
|
@ -68,3 +72,9 @@ let toStringResult = x =>
|
|||
| Ok(a) => `Ok(${toString(a)})`
|
||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
||||
let toStringResultRecord = x =>
|
||||
switch x {
|
||||
| Ok(a) => `Ok(${toStringRecord(a)})`
|
||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ module ExpressionValue = ReducerInterface_ExpressionValue
|
|||
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
|
||||
module Sample = {
|
||||
// In real life real libraries should be somewhere else
|
||||
/*
|
||||
For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||
*/
|
||||
let customAdd = (a: float, b: float): float => {a +. b}
|
||||
}
|
||||
// module Sample = {
|
||||
// // In real life real libraries should be somewhere else
|
||||
// /*
|
||||
// For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||
// */
|
||||
// let customAdd = (a: float, b: float): float => {a +. b}
|
||||
// }
|
||||
|
||||
/*
|
||||
Map external calls of Reducer
|
||||
|
|
|
@ -14639,6 +14639,11 @@ reading-time@^1.5.0:
|
|||
resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb"
|
||||
integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==
|
||||
|
||||
reanalyze@^2.19.0:
|
||||
version "2.19.0"
|
||||
resolved "https://registry.yarnpkg.com/reanalyze/-/reanalyze-2.19.0.tgz#b34b16bd7249a07dc931bb3cc58410247bb36a03"
|
||||
integrity sha512-ycVr19BH6b2J70BmG6nUIzrFWiBNLUCmip6D2NEPLpnJimF6wagE+BIksI6vcTCovqWjgNMKhFCbZKwm+5/8VQ==
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
|
|
Loading…
Reference in New Issue
Block a user