refactor reducer
removed some extra array references rename Builder to ExpressionBuilder Expression Builder Trash Warning remove parsePartial/Outer, add context to lambda format module Bindings simplify types module Macro reduceValueList do macro call result map bindings stop replacing on macro calls Macro Test doBindStatement bind a statement bindings tested. TODO bind shadowing in lambda block tests defined block tests defined blocks tested macro lambda test defined
This commit is contained in:
parent
7b052ee3c3
commit
8e318a8aa9
|
@ -0,0 +1,121 @@
|
|||
open Jest
|
||||
// open Expect
|
||||
|
||||
open Reducer_Expression_ExpressionBuilder
|
||||
open Reducer_TestMacroHelpers
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
|
||||
let exampleExpression = eNumber(1.)
|
||||
let exampleExpressionY = eSymbol("y")
|
||||
let exampleStatement = eLetStatement("y", eNumber(1.))
|
||||
let exampleStatementX = eLetStatement("y", eSymbol("x"))
|
||||
let exampleStatementZ = eLetStatement("z", eSymbol("y"))
|
||||
|
||||
// If it is not a mactro then it is not expanded
|
||||
testMacro([], exampleExpression, "Ok(1)")
|
||||
|
||||
describe("bindStatement", () => {
|
||||
// A statement is bound by the bindings created by the previous statement
|
||||
testMacro([], eBindStatement(eBindings([]), exampleStatement), "Ok((:$setBindings {} :y 1))")
|
||||
// Then it answers the bindings for the next statement when reduced
|
||||
testMacroEval([], eBindStatement(eBindings([]), exampleStatement), "Ok({y: 1})")
|
||||
// Now let's feed a binding to see what happens
|
||||
testMacro(
|
||||
[],
|
||||
eBindStatement(eBindings([("x", EvNumber(2.))]), exampleStatementX),
|
||||
"Ok((:$setBindings {x: 2} :y 2))",
|
||||
)
|
||||
// An expression does not return a binding, thus error
|
||||
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Error(Assignment expected)")
|
||||
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
|
||||
testMacro(
|
||||
[("z", EvNumber(99.))],
|
||||
eBindStatementDefault(exampleStatement),
|
||||
"Ok((:$setBindings {z: 99} :y 1))",
|
||||
)
|
||||
})
|
||||
|
||||
describe("bindExpression", () => {
|
||||
// x is simply bound in the expression
|
||||
testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2)")
|
||||
// When an let statement is the end expression then bindings are returned
|
||||
testMacro(
|
||||
[],
|
||||
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatement),
|
||||
"Ok((:$exportBindings (:$setBindings {x: 2} :y 1)))",
|
||||
)
|
||||
// Now let's reduce that expression
|
||||
testMacroEval(
|
||||
[],
|
||||
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatement),
|
||||
"Ok({x: 2,y: 1})",
|
||||
)
|
||||
// When bindings are missing the context is injected. This must be the first and last statement of a block
|
||||
testMacroEval(
|
||||
[("z", EvNumber(99.))],
|
||||
eBindExpressionDefault(exampleStatement),
|
||||
"Ok({y: 1,z: 99})",
|
||||
)
|
||||
})
|
||||
|
||||
describe("block", () => {
|
||||
// Block with a single expression
|
||||
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$bindExpression 1))")
|
||||
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
|
||||
// Block with a single statement
|
||||
testMacro([], eBlock(list{exampleStatement}), "Ok((:$$bindExpression (:$let :y 1)))")
|
||||
testMacroEval([], eBlock(list{exampleStatement}), "Ok({y: 1})")
|
||||
// Block with a statement and an expression
|
||||
testMacro(
|
||||
[],
|
||||
eBlock(list{exampleStatement, exampleExpressionY}),
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) :y))",
|
||||
)
|
||||
testMacroEval([], eBlock(list{exampleStatement, exampleExpressionY}), "Ok(1)")
|
||||
// Block with a statement and another statement
|
||||
testMacro(
|
||||
[],
|
||||
eBlock(list{exampleStatement, exampleStatementZ}),
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) (:$let :z :y)))",
|
||||
)
|
||||
testMacroEval([], eBlock(list{exampleStatement, exampleStatementZ}), "Ok({y: 1,z: 1})")
|
||||
// Block inside a block
|
||||
testMacro(
|
||||
[],
|
||||
eBlock(list{eBlock(list{exampleExpression})}),
|
||||
"Ok((:$$bindExpression (:$$block 1)))",
|
||||
)
|
||||
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
|
||||
// Block assigned to a variable
|
||||
testMacro(
|
||||
[],
|
||||
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
||||
"Ok((:$$bindExpression (:$let :z (:$$block (:$$block :y)))))",
|
||||
)
|
||||
testMacroEval(
|
||||
[],
|
||||
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
||||
"Ok({z: :y})",
|
||||
)
|
||||
// Empty block
|
||||
testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error
|
||||
})
|
||||
|
||||
describe("lambda", () => {
|
||||
// assign a lambda to a variable
|
||||
let lambdaExpression = eFunction("$$lambda", list{eArrayString(["y"]), exampleExpressionY})
|
||||
testMacro([], lambdaExpression, "Ok(lambda(y=>internal))")
|
||||
// call a lambda
|
||||
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
|
||||
testMacro([], callLambdaExpression, "Ok(((:$$lambda [y] :y) 1))")
|
||||
testMacroEval([], callLambdaExpression, "Ok(1)")
|
||||
// Parameters shadow the outer scope
|
||||
testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(1)")
|
||||
// When not shadowed by the parameters, the outer scope variables are available
|
||||
let lambdaExpression = eFunction(
|
||||
"$$lambda",
|
||||
list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})},
|
||||
)
|
||||
let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)})
|
||||
testMacroEval([("y", EvNumber(666.))], callLambdaExpression, "Ok(667)")
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
open Jest
|
||||
open Expect
|
||||
|
||||
test("dummy", () => {
|
||||
expect(true)->toBe(true)
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
module Expression = Reducer.Expression
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module ErrorValue = Reducer_ErrorValue
|
||||
|
||||
|
@ -14,47 +14,18 @@ let unwrapRecord = rValue =>
|
|||
)
|
||||
|
||||
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)
|
||||
Reducer.parse(expr)->ExpressionT.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.evaluateUsingOptions(
|
||||
expr,
|
||||
~externalBindings=Some(bindings),
|
||||
~isPartial=None,
|
||||
~environment=None,
|
||||
)
|
||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
||||
->ExpressionValue.toStringResult
|
||||
->expect
|
||||
->toBe(answer)
|
||||
|
||||
let expectEvalPartialBindingsToBe = (
|
||||
expr: string,
|
||||
bindings: Reducer.externalBindings,
|
||||
answer: string,
|
||||
) =>
|
||||
Reducer.evaluateUsingOptions(
|
||||
expr,
|
||||
~externalBindings=Some(bindings),
|
||||
~isPartial=Some(true),
|
||||
~environment=None,
|
||||
)
|
||||
->unwrapRecord
|
||||
->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))
|
||||
|
||||
|
@ -62,34 +33,16 @@ let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answe
|
|||
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,81 @@
|
|||
open Jest
|
||||
open Expect
|
||||
|
||||
module Macro = Reducer_Expression_Macro
|
||||
module Bindings = Reducer_Expression_Bindings
|
||||
module Expression = Reducer_Expression
|
||||
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||
module T = Reducer_Expression_T
|
||||
|
||||
let testMacro_ = (
|
||||
tester,
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedCode: string,
|
||||
) => {
|
||||
let bindings = Belt.Map.String.fromArray(bindArray)
|
||||
tester(expr->T.toString, () =>
|
||||
expr
|
||||
->Macro.expandMacroCall(
|
||||
bindings,
|
||||
ExpressionValue.defaultEnvironment,
|
||||
Expression.reduceExpression,
|
||||
)
|
||||
->T.toStringResult
|
||||
->expect
|
||||
->toEqual(expectedCode)
|
||||
)
|
||||
}
|
||||
|
||||
let testMacroEval_ = (
|
||||
tester,
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedValue: string,
|
||||
) => {
|
||||
let bindings = Belt.Map.String.fromArray(bindArray)
|
||||
tester(expr->T.toString, () =>
|
||||
expr
|
||||
->Macro.doMacroCall(bindings, ExpressionValue.defaultEnvironment, Expression.reduceExpression)
|
||||
->ExpressionValue.toStringResult
|
||||
->expect
|
||||
->toEqual(expectedValue)
|
||||
)
|
||||
}
|
||||
|
||||
let testMacro = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedExpr: string,
|
||||
) => testMacro_(test, bindArray, expr, expectedExpr)
|
||||
let testMacroEval = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedValue: string,
|
||||
) => testMacroEval_(test, bindArray, expr, expectedValue)
|
||||
|
||||
module MySkip = {
|
||||
let testMacro = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedExpr: string,
|
||||
) => testMacro_(Skip.test, bindArray, expr, expectedExpr)
|
||||
let testMacroEval = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedValue: string,
|
||||
) => testMacroEval_(Skip.test, bindArray, expr, expectedValue)
|
||||
}
|
||||
|
||||
module MyOnly = {
|
||||
let testMacro = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedExpr: string,
|
||||
) => testMacro_(Only.test, bindArray, expr, expectedExpr)
|
||||
let testMacroEval = (
|
||||
bindArray: array<(string, ExpressionValue.expressionValue)>,
|
||||
expr: T.expression,
|
||||
expectedValue: string,
|
||||
) => testMacroEval_(Only.test, bindArray, expr, expectedValue)
|
||||
}
|
|
@ -1,33 +1,34 @@
|
|||
// TODO: Reimplement with usual parse
|
||||
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 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("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)")
|
||||
|
@ -39,22 +40,22 @@ describe("Eval with Bindings", () => {
|
|||
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})",
|
||||
)
|
||||
})
|
||||
// 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})",
|
||||
// )
|
||||
// })
|
||||
|
|
|
@ -2,8 +2,8 @@ open Jest
|
|||
open Reducer_TestHelpers
|
||||
|
||||
describe("Parse function assignment", () => {
|
||||
testParseToBe("f(x)=x", "Ok((:$let :f (:$lambda (x) :x)))")
|
||||
testParseToBe("f(x)=2*x", "Ok((:$let :f (:$lambda (x) (:multiply 2 :x))))")
|
||||
testParseToBe("f(x)=x", "Ok((:$$block (:$let :f (:$$lambda [x] (:$$block :x)))))")
|
||||
testParseToBe("f(x)=2*x", "Ok((:$$block (:$let :f (:$$lambda [x] (:$$block (:multiply 2 :x))))))")
|
||||
//MathJs does not allow blocks in function definitions
|
||||
})
|
||||
|
||||
|
|
|
@ -10,46 +10,39 @@ describe("reducer using mathjs parse", () => {
|
|||
// Those tests toString that we are converting mathjs parse tree to what we need
|
||||
|
||||
describe("expressions", () => {
|
||||
testParseToBe("1", "Ok(1)")
|
||||
testParseToBe("(1)", "Ok(1)")
|
||||
testParseToBe("1+2", "Ok((:add 1 2))")
|
||||
testParseToBe("1+2", "Ok((:add 1 2))")
|
||||
testParseToBe("1+2", "Ok((:add 1 2))")
|
||||
testParseToBe("1+2*3", "Ok((:add 1 (:multiply 2 3)))")
|
||||
testParseToBe("1", "Ok((:$$block 1))")
|
||||
testParseToBe("(1)", "Ok((:$$block 1))")
|
||||
testParseToBe("1+2", "Ok((:$$block (:add 1 2)))")
|
||||
testParseToBe("1+2*3", "Ok((:$$block (:add 1 (:multiply 2 3))))")
|
||||
})
|
||||
describe("arrays", () => {
|
||||
//Note. () is a empty list in Lisp
|
||||
// The only builtin structure in Lisp is list. There are no arrays
|
||||
// [1,2,3] becomes (1 2 3)
|
||||
testDescriptionParseToBe("empty", "[]", "Ok(())")
|
||||
testParseToBe("[1, 2, 3]", "Ok((1 2 3))")
|
||||
testParseToBe("['hello', 'world']", "Ok(('hello' 'world'))")
|
||||
testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$atIndex (0 1 2) (1)))")
|
||||
testDescriptionParseToBe("empty", "[]", "Ok((:$$block ()))")
|
||||
testParseToBe("[1, 2, 3]", "Ok((:$$block (1 2 3)))")
|
||||
testParseToBe("['hello', 'world']", "Ok((:$$block ('hello' 'world')))")
|
||||
testDescriptionParseToBe("index", "([0,1,2])[1]", "Ok((:$$block (:$atIndex (0 1 2) (1))))")
|
||||
})
|
||||
describe("records", () => {
|
||||
testDescriptionParseToBe("define", "{a: 1, b: 2}", "Ok((:$constructRecord (('a' 1) ('b' 2))))")
|
||||
testDescriptionParseToBe(
|
||||
"define",
|
||||
"{a: 1, b: 2}",
|
||||
"Ok((:$$block (:$constructRecord (('a' 1) ('b' 2)))))",
|
||||
)
|
||||
testDescriptionParseToBe(
|
||||
"use",
|
||||
"{a: 1, b: 2}.a",
|
||||
"Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))",
|
||||
"Ok((:$$block (:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a'))))",
|
||||
)
|
||||
})
|
||||
describe("multi-line", () => {
|
||||
testParseToBe("1; 2", "Ok((:$$bindExpression (:$$bindStatement (:$$bindings) 1) 2))")
|
||||
testParseToBe(
|
||||
"1+1; 2+1",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:add 1 1)) (:add 2 1)))",
|
||||
)
|
||||
testParseToBe("1; 2", "Ok((:$$block (:$$block 1 2)))")
|
||||
testParseToBe("1+1; 2+1", "Ok((:$$block (:$$block (:add 1 1) (:add 2 1))))")
|
||||
})
|
||||
describe("assignment", () => {
|
||||
testParseToBe(
|
||||
"x=1; x",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x 1)) :x))",
|
||||
)
|
||||
testParseToBe(
|
||||
"x=1+1; x+1",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :x (:add 1 1))) (:add :x 1)))",
|
||||
)
|
||||
testParseToBe("x=1; x", "Ok((:$$block (:$$block (:$let :x 1) :x)))")
|
||||
testParseToBe("x=1+1; x+1", "Ok((:$$block (:$$block (:$let :x (:add 1 1)) (:add :x 1))))")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -91,7 +84,7 @@ describe("eval", () => {
|
|||
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
|
||||
testEvalToBe("1; x=1", "Error(Assignment expected)")
|
||||
testEvalToBe("1; 1", "Error(Assignment expected)")
|
||||
testEvalToBe("x=1; x=1", "Error(Expression expected)")
|
||||
testEvalToBe("x=1; x=1", "Ok({x: 1})")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -119,27 +119,33 @@ describe("eval on distribution functions", () => {
|
|||
|
||||
describe("parse on distribution functions", () => {
|
||||
describe("power", () => {
|
||||
testParse("normal(5,2) ^ normal(5,1)", "Ok((:pow (:normal 5 2) (:normal 5 1)))")
|
||||
testParse("3 ^ normal(5,1)", "Ok((:pow 3 (:normal 5 1)))")
|
||||
testParse("normal(5,2) ^ 3", "Ok((:pow (:normal 5 2) 3))")
|
||||
testParse("normal(5,2) ^ normal(5,1)", "Ok((:$$block (:pow (:normal 5 2) (:normal 5 1))))")
|
||||
testParse("3 ^ normal(5,1)", "Ok((:$$block (:pow 3 (:normal 5 1))))")
|
||||
testParse("normal(5,2) ^ 3", "Ok((:$$block (:pow (:normal 5 2) 3)))")
|
||||
})
|
||||
describe("subtraction", () => {
|
||||
testParse("10 - normal(5,1)", "Ok((:subtract 10 (:normal 5 1)))")
|
||||
testParse("normal(5,1) - 10", "Ok((:subtract (:normal 5 1) 10))")
|
||||
testParse("10 - normal(5,1)", "Ok((:$$block (:subtract 10 (:normal 5 1))))")
|
||||
testParse("normal(5,1) - 10", "Ok((:$$block (:subtract (:normal 5 1) 10)))")
|
||||
})
|
||||
describe("pointwise arithmetic expressions", () => {
|
||||
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
||||
testParse(
|
||||
~skip=true,
|
||||
"normal(5,2) .- normal(5,1)",
|
||||
"Ok((:dotSubtract (:normal 5 2) (:normal 5 1)))",
|
||||
"Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))",
|
||||
)
|
||||
testParse("normal(5,2) .* normal(5,1)", "Ok((:dotMultiply (:normal 5 2) (:normal 5 1)))")
|
||||
testParse("normal(5,2) ./ normal(5,1)", "Ok((:dotDivide (:normal 5 2) (:normal 5 1)))")
|
||||
testParse("normal(5,2) .^ normal(5,1)", "Ok((:dotPow (:normal 5 2) (:normal 5 1)))")
|
||||
testParse(
|
||||
"normal(5,2) .* normal(5,1)",
|
||||
"Ok((:$$block (:dotMultiply (:normal 5 2) (:normal 5 1))))",
|
||||
)
|
||||
testParse(
|
||||
"normal(5,2) ./ normal(5,1)",
|
||||
"Ok((:$$block (:dotDivide (:normal 5 2) (:normal 5 1))))",
|
||||
)
|
||||
testParse("normal(5,2) .^ normal(5,1)", "Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))")
|
||||
})
|
||||
describe("equality", () => {
|
||||
testParse("5 == normal(5,2)", "Ok((:equal 5 (:normal 5 2)))")
|
||||
testParse("5 == normal(5,2)", "Ok((:$$block (:equal 5 (:normal 5 2))))")
|
||||
})
|
||||
describe("pointwise adding two normals", () => {
|
||||
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
||||
|
|
|
@ -12,5 +12,3 @@ type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
|||
let evaluate = Expression.evaluate
|
||||
let evaluateUsingOptions = Expression.evaluateUsingOptions
|
||||
let parse = Expression.parse
|
||||
let parseOuter = Expression.parseOuter
|
||||
let parsePartial = Expression.parsePartial
|
||||
|
|
|
@ -18,12 +18,9 @@ type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
|||
let evaluateUsingOptions: (
|
||||
~environment: option<QuriSquiggleLang.ReducerInterface_ExpressionValue.environment>,
|
||||
~externalBindings: option<QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings>,
|
||||
~isPartial: option<bool>,
|
||||
string,
|
||||
) => 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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module ExternalLibrary = ReducerInterface.ExternalLibrary
|
||||
module MathJs = Reducer_MathJs
|
||||
module Bindings = Reducer_Expression_Bindings
|
||||
open ReducerInterface.ExpressionValue
|
||||
open Reducer_ErrorValue
|
||||
|
||||
|
@ -68,6 +69,20 @@ let callInternal = (call: functionCall, _environment): result<'b, errorValue> =>
|
|||
value->Ok
|
||||
}
|
||||
|
||||
let doSetBindings = (
|
||||
externalBindings: externalBindings,
|
||||
symbol: string,
|
||||
value: expressionValue,
|
||||
) => {
|
||||
Bindings.fromExternalBindings(externalBindings)
|
||||
->Belt.Map.String.set(symbol, value)
|
||||
->Bindings.toExternalBindings
|
||||
->EvRecord
|
||||
->Ok
|
||||
}
|
||||
|
||||
let doExportBindings = (externalBindings: externalBindings) => EvRecord(externalBindings)->Ok
|
||||
|
||||
switch call {
|
||||
| ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) =>
|
||||
arrayAtIndex(aValueArray, fIndex)
|
||||
|
@ -78,6 +93,9 @@ let callInternal = (call: functionCall, _environment): result<'b, errorValue> =>
|
|||
| ("inspect", [value, EvString(label)]) => inspectLabel(value, label)
|
||||
| ("inspect", [value]) => inspect(value)
|
||||
| ("inspectPerformance", [value, EvString(label)]) => inspectPerformance(value, label)
|
||||
| ("$setBindings", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
||||
doSetBindings(externalBindings, symbol, value)
|
||||
| ("$exportBindings", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
||||
| call => callMathJs(call)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,179 +3,143 @@
|
|||
they take expressions as parameters and return a new expression.
|
||||
Macros are used to define language building blocks. They are like Lisp macros.
|
||||
*/
|
||||
module Bindings = Reducer_Expression_Bindings
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Result = Belt.Result
|
||||
|
||||
open Reducer_ErrorValue
|
||||
open Reducer_Expression_ExpressionBuilder
|
||||
|
||||
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<
|
||||
expression,
|
||||
errorValue,
|
||||
> => {
|
||||
let getParameters = (bindings: ExpressionT.bindings): array<string> => {
|
||||
let eParameters = Belt.Map.String.getWithDefault(bindings, "$parameters", EParameters([]))
|
||||
switch eParameters {
|
||||
| EParameters(parameters) => parameters
|
||||
| _ => []
|
||||
}
|
||||
}
|
||||
|
||||
let putParameters = (
|
||||
bindings: ExpressionT.bindings,
|
||||
parameters: array<string>,
|
||||
): ExpressionT.bindings =>
|
||||
Belt.Map.String.set(bindings, "$parameters", ExpressionT.EParameters(parameters))
|
||||
|
||||
let answerBindingIfNotParameter = (aSymbol, defaultExpression, parameters, bindings) =>
|
||||
switch Js.Array2.some(parameters, a => a == aSymbol) {
|
||||
| true => defaultExpression->Ok // We cannot bind the parameters with global values
|
||||
| false =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => RESymbolNotFound(aSymbol)->Error
|
||||
}
|
||||
}
|
||||
|
||||
let answerCallBindingIfNotParameter = (aSymbol, defaultExpression, parameters, bindings) =>
|
||||
switch Js.Array2.some(parameters, a => a == aSymbol) {
|
||||
| true => defaultExpression->Ok // We cannot bind the parameters with global values
|
||||
| false =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => defaultExpression->Ok
|
||||
}
|
||||
}
|
||||
|
||||
switch expression {
|
||||
| ExpressionT.EValue(EvSymbol(aSymbol)) => {
|
||||
let parameters = getParameters(bindings)
|
||||
answerBindingIfNotParameter(aSymbol, expression, parameters, bindings)
|
||||
}
|
||||
| ExpressionT.EValue(EvCall(aSymbol)) => {
|
||||
let parameters = getParameters(bindings)
|
||||
answerCallBindingIfNotParameter(aSymbol, expression, parameters, bindings)
|
||||
}
|
||||
| ExpressionT.EValue(_) => expression->Ok
|
||||
| ExpressionT.EBindings(_) => expression->Ok
|
||||
| ExpressionT.EParameters(_) => expression->Ok
|
||||
| ExpressionT.EList(list{
|
||||
ExpressionT.EValue(EvCall("$lambda")),
|
||||
ExpressionT.EParameters(parameters),
|
||||
expr,
|
||||
}) => {
|
||||
let oldParameters = getParameters(bindings)
|
||||
let newParameters = oldParameters->Js.Array2.concat(parameters)
|
||||
let newBindings = putParameters(bindings, newParameters)
|
||||
let rNewExpr = replaceSymbols(expr, newBindings)
|
||||
rNewExpr->Result.flatMap(newExpr =>
|
||||
ExpressionT.EList(list{
|
||||
ExpressionT.EValue(EvCall("$lambda")),
|
||||
ExpressionT.EParameters(parameters),
|
||||
newExpr,
|
||||
})->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)
|
||||
}
|
||||
}
|
||||
}
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
|
||||
let dispatchMacroCall = (
|
||||
list: list<expression>,
|
||||
macroExpression: expression,
|
||||
bindings: ExpressionT.bindings,
|
||||
environment,
|
||||
reduceExpression: reducerFn,
|
||||
): result<expression, 'e> => {
|
||||
let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => {
|
||||
reduceExpression: ExpressionT.reducerFn,
|
||||
): result<expression, errorValue> => {
|
||||
let doBindStatement = (bindingExpr: expression, statement: expression, environment) =>
|
||||
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, environment)
|
||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => {
|
||||
let rExternalBindingsValue = reduceExpression(
|
||||
bindingExpr,
|
||||
Bindings.defaultBindings,
|
||||
environment,
|
||||
)
|
||||
|
||||
let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue))
|
||||
rNewExpression->Result.map(newExpression =>
|
||||
Belt.Map.String.set(bindings, aSymbol, newExpression)->ExpressionT.EBindings
|
||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
||||
rNewStatement->Result.map(newStatement =>
|
||||
eFunction(
|
||||
"$setBindings",
|
||||
list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement},
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
| _ => 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 doBindExpression = (bindingExpr: expression, statement: expression, environment) =>
|
||||
switch statement {
|
||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), symbolExpr, statement}) => {
|
||||
let rExternalBindingsValue = reduceExpression(
|
||||
bindingExpr,
|
||||
Bindings.defaultBindings,
|
||||
environment,
|
||||
)
|
||||
|
||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
||||
rNewStatement->Result.map(newStatement =>
|
||||
eFunction(
|
||||
"$exportBindings",
|
||||
list{
|
||||
eFunction(
|
||||
"$setBindings",
|
||||
list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement},
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
let externalBindings = reducedBindings->Belt.Map.String.reduce(emptyDictionary, (
|
||||
acc,
|
||||
key,
|
||||
expressionValue,
|
||||
) => {
|
||||
let value = switch expressionValue {
|
||||
| ExpressionT.EValue(aValue) => aValue
|
||||
| _ => EvSymbol("internal")
|
||||
}
|
||||
Js.Dict.set(acc, key, value)
|
||||
acc
|
||||
})
|
||||
externalBindings->ExpressionValue.EvRecord->ExpressionT.EValue->Ok
|
||||
}
|
||||
| _ => {
|
||||
let rExternalBindingsValue = reduceExpression(
|
||||
bindingExpr,
|
||||
Bindings.defaultBindings,
|
||||
environment,
|
||||
)
|
||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
||||
rNewStatement
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
let doBlock = (exprs: list<expression>, _bindings: ExpressionT.bindings, _environment): result<
|
||||
expression,
|
||||
errorValue,
|
||||
> => {
|
||||
let exprsArray = Belt.List.toArray(exprs)
|
||||
let maxIndex = Js.Array2.length(exprsArray) - 1
|
||||
exprsArray->Js.Array2.reducei((acc, statement, index) =>
|
||||
if index == 0 {
|
||||
if index == maxIndex {
|
||||
eBindExpressionDefault(statement)
|
||||
} else {
|
||||
eBindStatementDefault(statement)
|
||||
}
|
||||
} else if index == maxIndex {
|
||||
eBindExpression(acc, statement)
|
||||
} else {
|
||||
eBindStatement(acc, statement)
|
||||
}
|
||||
, eSymbol("undefined block"))->Ok
|
||||
}
|
||||
|
||||
switch list {
|
||||
| list{ExpressionT.EValue(EvCall("$$bindings"))} => bindings->ExpressionT.EBindings->Ok
|
||||
let doLambdaDefinition = (
|
||||
bindings: ExpressionT.bindings,
|
||||
parameters: array<string>,
|
||||
lambdaDefinition: ExpressionT.expression,
|
||||
) => eLambda(parameters, bindings->Bindings.toExternalBindings, lambdaDefinition)->Ok
|
||||
|
||||
let expandExpressionList = (aList, bindings: ExpressionT.bindings, environment) =>
|
||||
switch aList {
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindStatement")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
bindingExpr: ExpressionT.expression,
|
||||
statement,
|
||||
} =>
|
||||
doBindStatement(statement, bindings)
|
||||
doBindStatement(bindingExpr, statement, environment)
|
||||
| list{ExpressionT.EValue(EvCall("$$bindStatement")), statement} =>
|
||||
// bindings of the context are used when there is no binding expression
|
||||
doBindStatement(eRecord(Bindings.toExternalBindings(bindings)), statement, environment)
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindExpression")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
bindingExpr: ExpressionT.expression,
|
||||
expression,
|
||||
} =>
|
||||
doBindExpression(expression, bindings)
|
||||
| _ => list->ExpressionT.EList->Ok
|
||||
doBindExpression(bindingExpr, expression, environment)
|
||||
| list{ExpressionT.EValue(EvCall("$$bindExpression")), expression} =>
|
||||
// bindings of the context are used when there is no binding expression
|
||||
doBindExpression(eRecord(Bindings.toExternalBindings(bindings)), expression, environment)
|
||||
| list{ExpressionT.EValue(EvCall("$$block")), ...exprs} => doBlock(exprs, bindings, environment)
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$lambda")),
|
||||
ExpressionT.EValue(EvArrayString(parameters)),
|
||||
lambdaDefinition,
|
||||
} =>
|
||||
doLambdaDefinition(bindings, parameters, lambdaDefinition)
|
||||
| _ => ExpressionT.EList(aList)->Ok
|
||||
}
|
||||
|
||||
switch macroExpression {
|
||||
| EList(aList) => expandExpressionList(aList, bindings, environment)
|
||||
| _ => macroExpression->Ok
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
module Builder = Reducer_Expression_Builder
|
||||
module Bindings = Reducer_Expression_Bindings
|
||||
module BuiltIn = Reducer_Dispatch_BuiltIn
|
||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Extra = Reducer_Extra
|
||||
module Lambda = Reducer_Expression_Lambda
|
||||
module Macro = Reducer_Expression_Macro
|
||||
module MathJs = Reducer_MathJs
|
||||
module Result = Belt.Result
|
||||
module T = Reducer_Expression_T
|
||||
|
||||
open Reducer_ErrorValue
|
||||
|
||||
type environment = ReducerInterface_ExpressionValue.environment
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
type expression = T.expression
|
||||
|
@ -16,30 +17,6 @@ type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
|||
type internalCode = ReducerInterface_ExpressionValue.internalCode
|
||||
type t = expression
|
||||
|
||||
external castExpressionToInternalCode: expression => internalCode = "%identity"
|
||||
external castInternalCodeToExpression: internalCode => expression = "%identity"
|
||||
|
||||
/*
|
||||
Shows the expression as text of expression
|
||||
*/
|
||||
let rec toString = expression =>
|
||||
switch expression {
|
||||
| T.EBindings(_) => "$$bound"
|
||||
| T.EParameters(params) => `(${Js.Array2.toString(params)})`
|
||||
| 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
|
||||
*/
|
||||
|
@ -49,14 +26,6 @@ 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)
|
||||
*/
|
||||
|
@ -64,144 +33,63 @@ let rec reduceExpression = (expression: t, bindings: T.bindings, environment: en
|
|||
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, 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
|
||||
let zippedParameterList = parameterList->Belt.List.zip(args)
|
||||
let bindings = Belt.List.reduce(zippedParameterList, defaultBindings, (a, (p, e)) =>
|
||||
a->Belt.Map.String.set(p, e->T.EValue)
|
||||
)
|
||||
let newExpression = Builder.passToFunction(
|
||||
"$$bindExpression",
|
||||
list{Builder.passToFunction("$$bindings", list{}), expr},
|
||||
)
|
||||
reduceExpression(newExpression, bindings, environment)
|
||||
}
|
||||
|
||||
/*
|
||||
After reducing each level of expression(Lisp AST), we have a value list to evaluate
|
||||
*/
|
||||
let reduceValueList = (valueList: list<expressionValue>, environment): result<
|
||||
expressionValue,
|
||||
'e,
|
||||
> =>
|
||||
switch valueList {
|
||||
| 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, environment)
|
||||
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||
}
|
||||
|
||||
let rec seekMacros = (expression: t, bindings: T.bindings, environment): result<t, 'e> =>
|
||||
switch expression {
|
||||
| T.EValue(_value) => expression->Ok
|
||||
| T.EBindings(_value) => expression->Ok
|
||||
| T.EParameters(_value) => expression->Ok
|
||||
| T.EList(list) => {
|
||||
let racc: result<list<t>, 'e> = list->Belt.List.reduceReverse(Ok(list{}), (
|
||||
racc,
|
||||
each: expression,
|
||||
) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->seekMacros(bindings, environment)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.flatMap(acc => acc->doMacroCall(bindings, environment))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
| T.EValue(value) => value->Ok
|
||||
| T.EList(list) => {
|
||||
let racc: result<list<expressionValue>, 'e> = list->Belt.List.reduceReverse(Ok(list{}), (
|
||||
| T.EList(list) =>
|
||||
switch list {
|
||||
| list{EValue(EvCall(fName)), ..._args} =>
|
||||
switch Macro.isMacroName(fName) {
|
||||
// A macro expands then reduces itself
|
||||
| true => Macro.doMacroCall(expression, bindings, environment, reduceExpression)
|
||||
| false => reduceExpressionList(list, bindings, environment)
|
||||
}
|
||||
| _ => reduceExpressionList(list, bindings, environment)
|
||||
}
|
||||
}
|
||||
}
|
||||
and reduceExpressionList = (
|
||||
expressions: list<t>,
|
||||
bindings: T.bindings,
|
||||
environment: environment,
|
||||
): result<expressionValue, 'e> => {
|
||||
let racc: result<list<expressionValue>, 'e> = expressions->Belt.List.reduceReverse(Ok(list{}), (
|
||||
racc,
|
||||
each: expression,
|
||||
) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->reduceExpandedExpression(environment)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
->reduceExpression(bindings, environment)
|
||||
->Result.map(newNode => {
|
||||
acc->Belt.List.add(newNode)
|
||||
})
|
||||
})
|
||||
)
|
||||
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
|
||||
|
||||
/*
|
||||
After reducing each level of expression(Lisp AST), we have a value list to evaluate
|
||||
*/
|
||||
and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
||||
expressionValue,
|
||||
'e,
|
||||
> =>
|
||||
switch valueList {
|
||||
| list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment)
|
||||
|
||||
| list{EvLambda(lamdaCall), ...args} =>
|
||||
Lambda.doLambdaCall(lamdaCall, args, environment, reduceExpression)
|
||||
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||
}
|
||||
|
||||
let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings, environment)
|
||||
rExpandedExpression->Result.flatMap(expandedExpression =>
|
||||
expandedExpression->reduceExpandedExpression(environment)
|
||||
)
|
||||
}
|
||||
|
||||
let evalUsingExternalBindingsExpression_ = (aExpression, bindings, environment): result<
|
||||
let evalUsingBindingsExpression_ = (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 evalPartial_ = (codeText: string, bindings: T.bindings, environment: environment) => {
|
||||
parsePartial(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(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 statments are assignments.
|
||||
*/
|
||||
let evalOuter_ = (codeText: string, bindings: T.bindings, environment: environment) => {
|
||||
parseOuter(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(bindings, environment)
|
||||
)
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
let evaluateUsingOptions = (
|
||||
~environment: option<ReducerInterface_ExpressionValue.environment>,
|
||||
~externalBindings: option<ReducerInterface_ExpressionValue.externalBindings>,
|
||||
~isPartial: option<bool>,
|
||||
code: string,
|
||||
): result<expressionValue, errorValue> => {
|
||||
let anEnvironment = switch environment {
|
||||
|
@ -214,24 +102,15 @@ let evaluateUsingOptions = (
|
|||
| None => ReducerInterface_ExpressionValue.defaultExternalBindings
|
||||
}
|
||||
|
||||
let anIsPartial = switch isPartial {
|
||||
| Some(isPartial) => isPartial
|
||||
| None => false
|
||||
}
|
||||
let bindings = anExternalBindings->Bindings.fromExternalBindings
|
||||
|
||||
let bindings = anExternalBindings->externalBindingsToBindings
|
||||
|
||||
if anIsPartial {
|
||||
evalPartial_(code, bindings, anEnvironment)
|
||||
} else {
|
||||
evalOuter_(code, bindings, anEnvironment)
|
||||
}
|
||||
parse(code)->Result.flatMap(expr => evalUsingBindingsExpression_(expr, bindings, anEnvironment))
|
||||
}
|
||||
|
||||
/*
|
||||
Evaluates MathJs code and bindings via Reducer and answers the result
|
||||
*/
|
||||
let evaluate = (code: string): result<expressionValue, errorValue> => {
|
||||
evaluateUsingOptions(~environment=None, ~externalBindings=None, ~isPartial=None, code)
|
||||
evaluateUsingOptions(~environment=None, ~externalBindings=None, code)
|
||||
}
|
||||
let eval = evaluate
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Result = Belt.Result
|
||||
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
type expression = ExpressionT.expression
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||
|
||||
let defaultBindings: ExpressionT.bindings = Belt.Map.String.empty
|
||||
|
||||
let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.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, value)
|
||||
})
|
||||
}
|
||||
|
||||
let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings => {
|
||||
let keys = Belt.Map.String.keysToArray(bindings)
|
||||
keys->Belt.Array.reduce(Js.Dict.empty(), (acc, key) => {
|
||||
let value = bindings->Belt.Map.String.getExn(key)
|
||||
Js.Dict.set(acc, key, value)
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
let fromValue = (aValue: expressionValue) =>
|
||||
switch aValue {
|
||||
| EvRecord(externalBindings) => fromExternalBindings(externalBindings)
|
||||
| _ => defaultBindings
|
||||
}
|
||||
|
||||
let externalFromArray = anArray => Js.Dict.fromArray(anArray)
|
||||
|
||||
let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$")
|
||||
|
||||
let rec replaceSymbols = (bindings: ExpressionT.bindings, expression: expression): result<
|
||||
expression,
|
||||
errorValue,
|
||||
> =>
|
||||
switch expression {
|
||||
| ExpressionT.EValue(value) =>
|
||||
replaceSymbolOnValue(bindings, value)->Result.map(evValue => evValue->ExpressionT.EValue)
|
||||
| ExpressionT.EList(list) =>
|
||||
switch list {
|
||||
| list{EValue(EvCall(fName)), ..._args} =>
|
||||
switch isMacroName(fName) {
|
||||
// A macro reduces itself so we dont dive in it
|
||||
| true => expression->Ok
|
||||
| false => replaceSymbolsOnExpressionList(bindings, list)
|
||||
}
|
||||
| _ => replaceSymbolsOnExpressionList(bindings, list)
|
||||
}
|
||||
}
|
||||
|
||||
and replaceSymbolsOnExpressionList = (bindings, list) => {
|
||||
let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
replaceSymbols(bindings, each)->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.map(acc => acc->ExpressionT.EList)
|
||||
}
|
||||
and replaceSymbolOnValue = (bindings, evValue: expressionValue) =>
|
||||
switch evValue {
|
||||
| EvSymbol(symbol) | EvCall(symbol) =>
|
||||
Belt.Map.String.getWithDefault(bindings, symbol, evValue)->Ok
|
||||
| _ => evValue->Ok
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
module ErrorValue = Reducer_ErrorValue
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Result = Belt.Result
|
||||
|
||||
type errorValue = ErrorValue.errorValue
|
||||
type expression = ExpressionT.expression
|
||||
|
||||
let passToFunction = (fName: string, lispArgs: list<expression>): expression => {
|
||||
let toEvCallValue = (name: string): expression => name->ExpressionValue.EvCall->ExpressionT.EValue
|
||||
let fn = fName->toEvCallValue
|
||||
list{fn, ...lispArgs}->ExpressionT.EList
|
||||
}
|
||||
|
||||
let toEvSymbolValue = (name: string): expression =>
|
||||
name->ExpressionValue.EvSymbol->ExpressionT.EValue
|
|
@ -0,0 +1,60 @@
|
|||
module BBindings = Reducer_Expression_Bindings
|
||||
module BErrorValue = Reducer_ErrorValue
|
||||
module BExpressionT = Reducer_Expression_T
|
||||
module BExpressionValue = ReducerInterface.ExpressionValue
|
||||
|
||||
type errorValue = BErrorValue.errorValue
|
||||
type expression = BExpressionT.expression
|
||||
type internalCode = ReducerInterface_ExpressionValue.internalCode
|
||||
|
||||
external castExpressionToInternalCode: expression => internalCode = "%identity"
|
||||
|
||||
let eArray = anArray => anArray->BExpressionValue.EvArray->BExpressionT.EValue
|
||||
|
||||
let eArrayString = anArray => anArray->BExpressionValue.EvArrayString->BExpressionT.EValue
|
||||
|
||||
let eBindings = (anArray: array<(string, BExpressionValue.expressionValue)>) =>
|
||||
anArray->Js.Dict.fromArray->EvRecord->BExpressionT.EValue
|
||||
|
||||
let eBool = aBool => aBool->BExpressionValue.EvBool->BExpressionT.EValue
|
||||
|
||||
let eCall = (name: string): expression => name->BExpressionValue.EvCall->BExpressionT.EValue
|
||||
|
||||
let eFunction = (fName: string, lispArgs: list<expression>): expression => {
|
||||
let fn = fName->eCall
|
||||
list{fn, ...lispArgs}->BExpressionT.EList
|
||||
}
|
||||
|
||||
let eLambda = (parameters: array<string>, context, expr) =>
|
||||
BExpressionValue.EvLambda(
|
||||
parameters,
|
||||
context,
|
||||
expr->castExpressionToInternalCode,
|
||||
)->BExpressionT.EValue
|
||||
|
||||
let eNumber = aNumber => aNumber->BExpressionValue.EvNumber->BExpressionT.EValue
|
||||
|
||||
let eRecord = aRecord => aRecord->BExpressionValue.EvRecord->BExpressionT.EValue
|
||||
|
||||
let eString = aString => aString->BExpressionValue.EvString->BExpressionT.EValue
|
||||
|
||||
let eSymbol = (name: string): expression => name->BExpressionValue.EvSymbol->BExpressionT.EValue
|
||||
|
||||
let eList = (list: list<expression>): expression => list->BExpressionT.EList
|
||||
|
||||
let eBlock = (exprs: list<expression>): expression => eFunction("$$block", exprs)
|
||||
|
||||
let eLetStatement = (symbol: string, valueExpression: expression): expression =>
|
||||
eFunction("$let", list{eSymbol(symbol), valueExpression})
|
||||
|
||||
let eBindStatement = (bindingExpr: expression, letStatement: expression): expression =>
|
||||
eFunction("$$bindStatement", list{bindingExpr, letStatement})
|
||||
|
||||
let eBindStatementDefault = (letStatement: expression): expression =>
|
||||
eFunction("$$bindStatement", list{letStatement})
|
||||
|
||||
let eBindExpression = (bindingExpr: expression, expression: expression): expression =>
|
||||
eFunction("$$bindExpression", list{bindingExpr, expression})
|
||||
|
||||
let eBindExpressionDefault = (expression: expression): expression =>
|
||||
eFunction("$$bindExpression", list{expression})
|
|
@ -0,0 +1,35 @@
|
|||
module Bindings = Reducer_Expression_Bindings
|
||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
|
||||
type environment = ReducerInterface_ExpressionValue.environment
|
||||
type expression = ExpressionT.expression
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||
type internalCode = ReducerInterface_ExpressionValue.internalCode
|
||||
|
||||
external castInternalCodeToExpression: internalCode => expression = "%identity"
|
||||
|
||||
let applyParametersToLambda = (
|
||||
internal: internalCode,
|
||||
parameters: array<string>,
|
||||
args: list<expressionValue>,
|
||||
context: externalBindings,
|
||||
environment,
|
||||
reducer: ExpressionT.reducerFn,
|
||||
): result<expressionValue, 'e> => {
|
||||
let expr = castInternalCodeToExpression(internal)
|
||||
let parameterList = parameters->Belt.List.fromArray
|
||||
let zippedParameterList = parameterList->Belt.List.zip(args)
|
||||
let bindings = Belt.List.reduce(zippedParameterList, context->Bindings.fromExternalBindings, (
|
||||
acc,
|
||||
(variable, variableValue),
|
||||
) => acc->Belt.Map.String.set(variable, variableValue))
|
||||
let newExpression = ExpressionBuilder.eBlock(list{expr})
|
||||
reducer(newExpression, bindings, environment)
|
||||
}
|
||||
|
||||
let doLambdaCall = ((parameters, context, internal), args, environment, reducer) => {
|
||||
applyParametersToLambda(internal, parameters, args, context, environment, reducer)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Result = Belt.Result
|
||||
|
||||
type environment = ExpressionValue.environment
|
||||
type expression = ExpressionT.expression
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
|
||||
let expandMacroCall = (
|
||||
macroExpression: expression,
|
||||
bindings: ExpressionT.bindings,
|
||||
environment: environment,
|
||||
reduceExpression: ExpressionT.reducerFn,
|
||||
): result<expression, 'e> =>
|
||||
Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(
|
||||
macroExpression,
|
||||
bindings,
|
||||
environment,
|
||||
reduceExpression,
|
||||
)
|
||||
|
||||
let doMacroCall = (
|
||||
macroExpression: expression,
|
||||
bindings: ExpressionT.bindings,
|
||||
environment: environment,
|
||||
reduceExpression: ExpressionT.reducerFn,
|
||||
): result<expressionValue, 'e> =>
|
||||
expandMacroCall(
|
||||
macroExpression,
|
||||
bindings,
|
||||
environment,
|
||||
reduceExpression,
|
||||
)->Result.flatMap(expression => reduceExpression(expression, bindings, environment))
|
||||
|
||||
let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$")
|
|
@ -1,5 +1,3 @@
|
|||
open ReducerInterface.ExpressionValue
|
||||
|
||||
/*
|
||||
An expression is a Lisp AST. An expression is either a primitive value or a list of expressions.
|
||||
In the case of a list of expressions (e1, e2, e3, ...eN), the semantic is
|
||||
|
@ -8,9 +6,51 @@ open ReducerInterface.ExpressionValue
|
|||
A Lisp AST contains only expressions/primitive values to apply to their left.
|
||||
The act of defining the semantics of a functional language is to write it in terms of Lisp AST.
|
||||
*/
|
||||
module Extra = Reducer_Extra
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
type environment = ExpressionValue.environment
|
||||
|
||||
type rec expression =
|
||||
| EList(list<expression>) // A list to map-reduce
|
||||
| EValue(expressionValue) // Irreducible built-in value. Reducer should not know the internals. External libraries are responsible
|
||||
| EBindings(bindings) // $let kind of statements return bindings; for internal use only
|
||||
| EParameters(array<string>) // for $defun; for internal use only
|
||||
and bindings = Belt.Map.String.t<expression>
|
||||
and bindings = Belt.Map.String.t<expressionValue>
|
||||
|
||||
type reducerFn = (
|
||||
expression,
|
||||
bindings,
|
||||
environment,
|
||||
) => result<expressionValue, Reducer_ErrorValue.errorValue>
|
||||
|
||||
/*
|
||||
Converts the expression to String
|
||||
*/
|
||||
let rec toString = expression =>
|
||||
switch expression {
|
||||
| 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(${Reducer_ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
||||
let inspect = (expr: expression): expression => {
|
||||
Js.log(toString(expr))
|
||||
expr
|
||||
}
|
||||
|
||||
let inspectResult = (r: result<expression, Reducer_ErrorValue.errorValue>): result<
|
||||
expression,
|
||||
Reducer_ErrorValue.errorValue,
|
||||
> => {
|
||||
Js.log(toStringResult(r))
|
||||
r
|
||||
}
|
||||
|
|
|
@ -1,37 +1,34 @@
|
|||
module Builder = Reducer_Expression_Builder
|
||||
/* * WARNING. DO NOT EDIT, BEAUTIFY, COMMENT ON OR REFACTOR THIS CODE.
|
||||
We will stop using MathJs parser and
|
||||
this whole file will go to trash
|
||||
**/
|
||||
module ErrorValue = Reducer_ErrorValue
|
||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module JavaScript = Reducer_Js
|
||||
module Parse = Reducer_MathJs_Parse
|
||||
module Result = Belt.Result
|
||||
|
||||
type errorValue = ErrorValue.errorValue
|
||||
type expression = ExpressionT.expression
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
type errorValue = ErrorValue.errorValue
|
||||
|
||||
type blockTag =
|
||||
| ImportVariablesStatement
|
||||
| ExportVariablesExpression
|
||||
type tagOrNode =
|
||||
| BlockTag(blockTag)
|
||||
| BlockNode(Parse.node)
|
||||
let blockToNode = block => block["node"]
|
||||
|
||||
let toTagOrNode = block => BlockNode(block["node"])
|
||||
|
||||
let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
||||
let rec fromInnerNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
||||
Belt.List.reduceReverse(nodeList, Ok(list{}), (racc, currNode) =>
|
||||
racc->Result.flatMap(acc =>
|
||||
fromNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
||||
)
|
||||
)
|
||||
|
||||
let caseFunctionNode = fNode => {
|
||||
let rLispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
||||
rLispArgs->Result.flatMap(lispArgs =>
|
||||
Builder.passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs)->Ok
|
||||
rLispArgs->Result.map(lispArgs =>
|
||||
ExpressionBuilder.eFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -42,18 +39,15 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
(key: string, value: Parse.node),
|
||||
) =>
|
||||
racc->Result.flatMap(acc =>
|
||||
fromNode(value)->Result.map(valueExpression => {
|
||||
fromInnerNode(value)->Result.map(valueExpression => {
|
||||
let entryCode =
|
||||
list{
|
||||
key->ExpressionValue.EvString->ExpressionT.EValue,
|
||||
valueExpression,
|
||||
}->ExpressionT.EList
|
||||
list{ExpressionBuilder.eString(key), valueExpression}->ExpressionT.EList
|
||||
list{entryCode, ...acc}
|
||||
})
|
||||
)
|
||||
)
|
||||
rargs->Result.flatMap(args =>
|
||||
Builder.passToFunction("$constructRecord", list{ExpressionT.EList(args)})->Ok
|
||||
ExpressionBuilder.eFunction("$constructRecord", list{ExpressionT.EList(args)})->Ok
|
||||
) // $constructRecord gets a single argument: List of key-value paiers
|
||||
}
|
||||
|
||||
|
@ -66,7 +60,7 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
Ok(list{}),
|
||||
(racc, currentPropertyMathJsNode) =>
|
||||
racc->Result.flatMap(acc =>
|
||||
fromNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{
|
||||
fromInnerNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{
|
||||
propertyCode,
|
||||
...acc,
|
||||
})
|
||||
|
@ -77,28 +71,41 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
|
||||
let caseAccessorNode = (objectNode, indexNode) => {
|
||||
caseIndexNode(indexNode)->Result.flatMap(indexCode => {
|
||||
fromNode(objectNode)->Result.flatMap(objectCode =>
|
||||
Builder.passToFunction("$atIndex", list{objectCode, indexCode})->Ok
|
||||
fromInnerNode(objectNode)->Result.flatMap(objectCode =>
|
||||
ExpressionBuilder.eFunction("$atIndex", list{objectCode, indexCode})->Ok
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let caseBlock = (nodesArray: array<Parse.node>): result<expression, errorValue> => {
|
||||
let rStatements: result<list<expression>, 'a> =
|
||||
nodesArray
|
||||
->Belt.List.fromArray
|
||||
->Belt.List.reduceReverse(Ok(list{}), (racc, currNode) =>
|
||||
racc->Result.flatMap(acc =>
|
||||
fromInnerNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
||||
)
|
||||
)
|
||||
rStatements->Result.map(statements => ExpressionBuilder.eBlock(statements))
|
||||
}
|
||||
|
||||
let caseAssignmentNode = aNode => {
|
||||
let symbol = aNode["object"]["name"]->Builder.toEvSymbolValue
|
||||
let rValueExpression = fromNode(aNode["value"])
|
||||
rValueExpression->Result.flatMap(valueExpression =>
|
||||
Builder.passToFunction("$let", list{symbol, valueExpression})->Ok
|
||||
let symbolName = aNode["object"]["name"]
|
||||
let rValueExpression = fromInnerNode(aNode["value"])
|
||||
rValueExpression->Result.map(valueExpression =>
|
||||
ExpressionBuilder.eLetStatement(symbolName, valueExpression)
|
||||
)
|
||||
}
|
||||
|
||||
let caseFunctionAssignmentNode = faNode => {
|
||||
let symbol = faNode["name"]->Builder.toEvSymbolValue
|
||||
let rValueExpression = fromNode(faNode["expr"])
|
||||
let symbol = faNode["name"]->ExpressionBuilder.eSymbol
|
||||
let rValueExpression = fromInnerNode(faNode["expr"])
|
||||
|
||||
rValueExpression->Result.flatMap(valueExpression => {
|
||||
let lispParams = faNode["params"]->ExpressionT.EParameters
|
||||
let lambda = Builder.passToFunction("$lambda", list{lispParams, valueExpression})
|
||||
Builder.passToFunction("$let", list{symbol, lambda})->Ok
|
||||
let lispParams = ExpressionBuilder.eArrayString(faNode["params"])
|
||||
let valueBlock = ExpressionBuilder.eBlock(list{valueExpression})
|
||||
let lambda = ExpressionBuilder.eFunction("$$lambda", list{lispParams, valueBlock})
|
||||
ExpressionBuilder.eFunction("$let", list{symbol, lambda})->Ok
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -111,11 +118,11 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
| MjArrayNode(aNode) => caseArrayNode(aNode)
|
||||
| MjAssignmentNode(aNode) => caseAssignmentNode(aNode)
|
||||
| MjSymbolNode(sNode) => {
|
||||
let expr: expression = Builder.toEvSymbolValue(sNode["name"])
|
||||
let expr: expression = ExpressionBuilder.eSymbol(sNode["name"])
|
||||
let rExpr: result<expression, errorValue> = expr->Ok
|
||||
rExpr
|
||||
}
|
||||
| MjBlockNode(bNode) => bNode["blocks"]->Belt.Array.map(toTagOrNode)->caseTagOrNodes
|
||||
| MjBlockNode(bNode) => bNode["blocks"]->Js.Array2.map(blockToNode)->caseBlock
|
||||
| MjConstantNode(cNode) =>
|
||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
||||
| MjFunctionAssignmentNode(faNode) => caseFunctionAssignmentNode(faNode)
|
||||
|
@ -123,78 +130,10 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
||||
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
||||
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->caseFunctionNode
|
||||
| MjParenthesisNode(pNode) => pNode["content"]->fromNode
|
||||
| MjParenthesisNode(pNode) => pNode["content"]->fromInnerNode
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
and caseTagOrNodes = (tagOrNodes): result<expression, errorValue> => {
|
||||
let initialBindings = Builder.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 =>
|
||||
Builder.passToFunction("$importVariablesStatement", list{})->Ok
|
||||
| ExportVariablesExpression =>
|
||||
Builder.passToFunction("$exportVariablesExpression", list{})->Ok
|
||||
}
|
||||
}
|
||||
|
||||
let bindName = if i == lastIndex {
|
||||
"$$bindExpression"
|
||||
} else {
|
||||
"$$bindStatement"
|
||||
}
|
||||
|
||||
rStatement->Result.flatMap((statement: expression) => {
|
||||
Builder.passToFunction(bindName, list{previousBindings, statement})->Ok
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
let fromNode = (node: Parse.node): result<expression, errorValue> =>
|
||||
fromInnerNode(node)->Result.map(expr => ExpressionBuilder.eBlock(list{expr}))
|
||||
|
|
|
@ -11,17 +11,19 @@ type internalCode = Object
|
|||
@genType
|
||||
type rec expressionValue =
|
||||
| EvArray(array<expressionValue>)
|
||||
| EvArrayString(array<string>)
|
||||
| EvBool(bool)
|
||||
| EvCall(string) // External function call
|
||||
| EvDistribution(DistributionTypes.genericDist)
|
||||
| EvLambda((array<string>, internalCode))
|
||||
| EvLambda((array<string>, record, internalCode))
|
||||
| EvNumber(float)
|
||||
| EvRecord(Js.Dict.t<expressionValue>)
|
||||
| EvRecord(record)
|
||||
| EvString(string)
|
||||
| EvSymbol(string)
|
||||
and record = Js.Dict.t<expressionValue>
|
||||
|
||||
@genType
|
||||
type externalBindings = Js.Dict.t<expressionValue>
|
||||
type externalBindings = record
|
||||
@genType
|
||||
let defaultExternalBindings: externalBindings = Js.Dict.empty()
|
||||
|
||||
|
@ -29,20 +31,21 @@ type functionCall = (string, array<expressionValue>)
|
|||
|
||||
let rec toString = aValue =>
|
||||
switch aValue {
|
||||
| EvArray(anArray) => {
|
||||
let args = anArray->Js.Array2.map(each => toString(each))->Js.Array2.toString
|
||||
`[${args}]`
|
||||
}
|
||||
| EvArrayString(anArray) => {
|
||||
let args = anArray->Js.Array2.toString
|
||||
`[${args}]`
|
||||
}
|
||||
| EvBool(aBool) => Js.String.make(aBool)
|
||||
| EvCall(fName) => `:${fName}`
|
||||
| EvLambda((parameters, _internalCode)) => `lambda(${Js.Array2.toString(parameters)}=>internal)`
|
||||
| EvLambda((parameters, _context, _internalCode)) =>
|
||||
`lambda(${Js.Array2.toString(parameters)}=>internal)`
|
||||
| EvNumber(aNumber) => Js.String.make(aNumber)
|
||||
| EvString(aString) => `'${aString}'`
|
||||
| EvSymbol(aString) => `:${aString}`
|
||||
| EvArray(anArray) => {
|
||||
let args =
|
||||
anArray
|
||||
->Belt.Array.map(each => toString(each))
|
||||
->Extra_Array.interperse(", ")
|
||||
->Js.String.concatMany("")
|
||||
`[${args}]`
|
||||
}
|
||||
| EvRecord(aRecord) => aRecord->toStringRecord
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
|
@ -50,19 +53,19 @@ and toStringRecord = aRecord => {
|
|||
let pairs =
|
||||
aRecord
|
||||
->Js.Dict.entries
|
||||
->Belt.Array.map(((eachKey, eachValue)) => `${eachKey}: ${toString(eachValue)}`)
|
||||
->Extra_Array.interperse(", ")
|
||||
->Js.String.concatMany("")
|
||||
->Js.Array2.map(((eachKey, eachValue)) => `${eachKey}: ${toString(eachValue)}`)
|
||||
->Js.Array2.toString
|
||||
`{${pairs}}`
|
||||
}
|
||||
|
||||
let toStringWithType = aValue =>
|
||||
switch aValue {
|
||||
| EvArray(_) => `Array::${toString(aValue)}`
|
||||
| EvArrayString(_) => `ArrayString::${toString(aValue)}`
|
||||
| EvBool(_) => `Bool::${toString(aValue)}`
|
||||
| EvCall(_) => `Call::${toString(aValue)}`
|
||||
| EvDistribution(_) => `Distribution::${toString(aValue)}`
|
||||
| EvLambda((_parameters, _internalCode)) => `Lambda::${toString(aValue)}`
|
||||
| EvLambda((_parameters, _context, _internalCode)) => `Lambda::${toString(aValue)}`
|
||||
| EvNumber(_) => `Number::${toString(aValue)}`
|
||||
| EvRecord(_) => `Record::${toString(aValue)}`
|
||||
| EvString(_) => `String::${toString(aValue)}`
|
||||
|
@ -70,7 +73,7 @@ let toStringWithType = aValue =>
|
|||
}
|
||||
|
||||
let argsToString = (args: array<expressionValue>): string => {
|
||||
args->Belt.Array.map(arg => arg->toString)->Extra_Array.interperse(", ")->Js.String.concatMany("")
|
||||
args->Js.Array2.map(arg => arg->toString)->Js.Array2.toString
|
||||
}
|
||||
|
||||
let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)})`
|
||||
|
|
Loading…
Reference in New Issue
Block a user