variables and statements
format only reducer reformat lint multi-line test spelling multi-line semantic mapping todo multi-line eval multi-line tests todo change context to bindings simplify tests rename exception test methods bindings is an expression value make bindings callable reformat Emphasize the nature of Lisp AST Initial definition of macros make functions private fixed functionNode type casting macro call skeleton sort ReducerInterface fix test macros skeleton bindings is not a value assignment semantics let semantics defined format reformat reformat TODO function calls and list hd variables are confused reformat tmp works reformat reformat add test reformat add test
This commit is contained in:
parent
1d550353c9
commit
660c0c70ae
|
@ -6,15 +6,18 @@ open Expect
|
|||
let expectEvalToBe = (expr: string, answer: string) =>
|
||||
Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||
|
||||
let testEval = (expr, answer) =>
|
||||
test(expr, () => expectEvalToBe(expr, answer))
|
||||
|
||||
describe("builtin", () => {
|
||||
// All MathJs operators and functions are available for string, number and boolean
|
||||
// .e.g + - / * > >= < <= == /= not and or
|
||||
// See https://mathjs.org/docs/expressions/syntax.html
|
||||
// See https://mathjs.org/docs/reference/functions.html
|
||||
test("-1", () => expectEvalToBe("-1", "Ok(-1)"))
|
||||
test("1-1", () => expectEvalToBe("1-1", "Ok(0)"))
|
||||
test("2>1", () => expectEvalToBe("2>1", "Ok(true)"))
|
||||
test("concat('a','b')", () => expectEvalToBe("concat('a','b')", "Ok('ab')"))
|
||||
testEval("-1", "Ok(-1)")
|
||||
testEval("1-1", "Ok(0)")
|
||||
testEval("2>1", "Ok(true)")
|
||||
testEval("concat('a','b')", "Ok('ab')")
|
||||
})
|
||||
|
||||
describe("builtin exception", () => {
|
||||
|
|
|
@ -11,9 +11,11 @@ let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)
|
|||
|
||||
let testDescParse = (desc, expr, answer) => test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let skipTestParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||
module MySkip = {
|
||||
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||
|
||||
let skipDescTestParse = (desc, expr, answer) => Skip.test(desc, () => expectParseToBe(expr, answer))
|
||||
let testDescParse = (desc, expr, answer) => Skip.test(desc, () => expectParseToBe(expr, answer))
|
||||
}
|
||||
|
||||
describe("MathJs parse", () => {
|
||||
describe("literals operators paranthesis", () => {
|
||||
|
@ -26,6 +28,10 @@ describe("MathJs parse", () => {
|
|||
testParse("(1+2)", "(add(1, 2))")
|
||||
})
|
||||
|
||||
describe("multi-line", () => {
|
||||
testParse("1; 2", "{1; 2}")
|
||||
})
|
||||
|
||||
describe("variables", () => {
|
||||
testParse("x = 1", "x = 1")
|
||||
testParse("x", "x")
|
||||
|
@ -33,16 +39,16 @@ describe("MathJs parse", () => {
|
|||
})
|
||||
|
||||
describe("functions", () => {
|
||||
skipTestParse("identity(x) = x", "???")
|
||||
skipTestParse("identity(x)", "???")
|
||||
MySkip.testParse("identity(x) = x", "???")
|
||||
MySkip.testParse("identity(x)", "???")
|
||||
})
|
||||
|
||||
describe("arrays", () => {
|
||||
test("empty", () => expectParseToBe("[]", "[]"))
|
||||
test("define", () => expectParseToBe("[0, 1, 2]", "[0, 1, 2]"))
|
||||
test("define with strings", () => expectParseToBe("['hello', 'world']", "['hello', 'world']"))
|
||||
skipTestParse("range(0, 4)", "range(0, 4)")
|
||||
test("index", () => expectParseToBe("([0,1,2])[1]", "([0, 1, 2])[1]"))
|
||||
testDescParse("empty", "[]", "[]")
|
||||
testDescParse("define", "[0, 1, 2]", "[0, 1, 2]")
|
||||
testDescParse("define with strings", "['hello', 'world']", "['hello', 'world']")
|
||||
MySkip.testParse("range(0, 4)", "range(0, 4)")
|
||||
testDescParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]")
|
||||
})
|
||||
|
||||
describe("records", () => {
|
||||
|
@ -51,10 +57,10 @@ describe("MathJs parse", () => {
|
|||
})
|
||||
|
||||
describe("comments", () => {
|
||||
skipDescTestParse("define", "# This is a comment", "???")
|
||||
MySkip.testDescParse("define", "# This is a comment", "???")
|
||||
})
|
||||
|
||||
describe("if statement", () => {
|
||||
skipDescTestParse("define", "if (true) { 1 } else { 0 }", "???")
|
||||
describe("if statement", () => { // TODO Tertiary operator instead
|
||||
MySkip.testDescParse("define", "if (true) { 1 } else { 0 }", "???")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -42,6 +42,15 @@ describe("reducer using mathjs parse", () => {
|
|||
"Ok((:$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)))")
|
||||
})
|
||||
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)))")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe("eval", () => {
|
||||
|
@ -71,13 +80,26 @@ describe("eval", () => {
|
|||
test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)"))
|
||||
test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)"))
|
||||
})
|
||||
|
||||
describe("multi-line", () => {
|
||||
testEvalToBe("1; 2", "Error(Assignment expected)")
|
||||
testEvalToBe("1+1; 2+1", "Error(Assignment expected)")
|
||||
})
|
||||
describe("assignment", () => {
|
||||
testEvalToBe("x=1; x", "Ok(1)")
|
||||
testEvalToBe("x=1+1; x+1", "Ok(3)")
|
||||
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)")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test exceptions", () => {
|
||||
testDescEvalToBe(
|
||||
"javascript exception",
|
||||
"jsraise('div by 0')",
|
||||
"javascriptraise('div by 0')",
|
||||
"Error(JS Exception: Error: 'div by 0')",
|
||||
)
|
||||
testDescEvalToBe("rescript exception", "resraise()", "Error(TODO: unhandled rescript exception)")
|
||||
testDescEvalToBe("rescript exception", "rescriptraise()", "Error(TODO: unhandled rescript exception)")
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"test:watch": "jest --watchAll",
|
||||
"coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html",
|
||||
"rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;",
|
||||
"reducer:format": "find src/rescript/Reducer src/rescript/ReducerInterface -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;",
|
||||
"all": "yarn build && yarn bundle && yarn test"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
@ -14,8 +14,8 @@ exception TestRescriptException
|
|||
let callInternal = (call: functionCall): result<'b, errorValue> => {
|
||||
let callMathJs = (call: functionCall): result<'b, errorValue> =>
|
||||
switch call {
|
||||
| ("jsraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests
|
||||
| ("resraise", _) => raise(TestRescriptException) // For Tests
|
||||
| ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests
|
||||
| ("rescriptraise", _) => raise(TestRescriptException) // For Tests
|
||||
| call => call->toStringFunctionCall->MathJs.Eval.eval
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
type errorValue =
|
||||
| REArrayIndexNotFound(string, int)
|
||||
| REAssignmentExpected
|
||||
| REExpressionExpected
|
||||
| REFunctionExpected(string)
|
||||
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
|
||||
| REMacroNotFound(string)
|
||||
| RERecordPropertyNotFound(string, string)
|
||||
| RESymbolNotFound(string)
|
||||
| RESyntaxError(string)
|
||||
| RETodo(string) // To do
|
||||
|
||||
|
@ -11,6 +15,8 @@ type t = errorValue
|
|||
let errorToString = err =>
|
||||
switch err {
|
||||
| REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}`
|
||||
| REAssignmentExpected => "Assignment expected"
|
||||
| REExpressionExpected => "Expression expected"
|
||||
| REFunctionExpected(msg) => `Function expected: ${msg}`
|
||||
| REJavaScriptExn(omsg, oname) => {
|
||||
let answer = "JS Exception:"
|
||||
|
@ -24,7 +30,9 @@ let errorToString = err =>
|
|||
}
|
||||
answer
|
||||
}
|
||||
| REMacroNotFound(macro) => `Macro not found: ${macro}`
|
||||
| RERecordPropertyNotFound(msg, index) => `${msg}: ${index}`
|
||||
| RESymbolNotFound(symbolName) => `${symbolName} is not defined`
|
||||
| RESyntaxError(desc) => `Syntax Error: ${desc}`
|
||||
| RETodo(msg) => `TODO: ${msg}`
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ type t = expression
|
|||
*/
|
||||
let rec toString = expression =>
|
||||
switch expression {
|
||||
| T.EBindings(bindings) => "$$bound"
|
||||
| T.EList(aList) =>
|
||||
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||
->Extra.List.interperse(" ")
|
||||
|
@ -38,24 +39,105 @@ let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
|||
let parse = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
||||
|
||||
module MapString = Belt.Map.String
|
||||
type bindings = MapString.t<unit>
|
||||
let defaultBindings: bindings = MapString.fromArray([])
|
||||
// TODO Define bindings for function execution context
|
||||
let defaultBindings: T.bindings = Belt.Map.String.empty
|
||||
|
||||
/*
|
||||
After reducing each level of code tree, we have a value list to evaluate
|
||||
Recursively evaluate/reduce the expression (Lisp AST)
|
||||
*/
|
||||
let reduceValueList = (valueList: list<expressionValue>): result<expressionValue, 'e> =>
|
||||
let rec reduceExpression = (expression: t, bindings: T.bindings): result<expressionValue, 'e> => {
|
||||
/*
|
||||
After reducing each level of expression(Lisp AST), we have a value list to evaluate
|
||||
*/
|
||||
let reduceValueList = (valueList: list<expressionValue>): result<expressionValue, 'e> =>
|
||||
switch valueList {
|
||||
| list{EvSymbol(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch
|
||||
| list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch
|
||||
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||
}
|
||||
|
||||
/*
|
||||
Recursively evaluate/reduce the code tree
|
||||
*/
|
||||
let rec reduceExpression = (expression: 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> => {
|
||||
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) =>
|
||||
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
|
||||
})
|
||||
})
|
||||
)
|
||||
->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->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(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)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.flatMap(acc => acc->doMacroCall(bindings))
|
||||
}
|
||||
}
|
||||
|
||||
let rec reduceExpandedExpression = (expression: t): result<expressionValue, 'e> =>
|
||||
switch expression {
|
||||
| T.EValue(value) => value->Ok
|
||||
| T.EList(list) => {
|
||||
|
@ -65,7 +147,7 @@ let rec reduceExpression = (expression: t, bindings): result<expressionValue, 'e
|
|||
) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->reduceExpression(bindings)
|
||||
->reduceExpandedExpression
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
|
@ -75,13 +157,19 @@ let rec reduceExpression = (expression: t, bindings): result<expressionValue, 'e
|
|||
}
|
||||
}
|
||||
|
||||
let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings)
|
||||
rExpandedExpression->Result.flatMap(expandedExpression =>
|
||||
expandedExpression->reduceExpandedExpression
|
||||
)
|
||||
}
|
||||
|
||||
let evalWBindingsExpression = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||
reduceExpression(aExpression, bindings)
|
||||
|
||||
/*
|
||||
Evaluates MathJs code via Lisp using bindings and answers the result
|
||||
*/
|
||||
let evalWBindings = (codeText: string, bindings: bindings) => {
|
||||
let evalWBindings = (codeText: string, bindings: T.bindings) => {
|
||||
parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
module Result = Belt.Result
|
||||
module T = Reducer_Expression_T
|
||||
type expression = T.expression
|
||||
type expressionValue = ReducerInterface.ExpressionValue.expressionValue
|
||||
type t = expression
|
||||
let toString: T.expression => Js.String.t
|
||||
let toStringResult: result<T.expression, 'a> => string
|
||||
let parse: string => result<expression, Reducer_ErrorValue.t>
|
||||
module MapString = Belt.Map.String
|
||||
type bindings = MapString.t<unit>
|
||||
let defaultBindings: bindings
|
||||
let reduceValueList: list<expressionValue> => result<expressionValue, Reducer_ErrorValue.t>
|
||||
let reduceExpression: (expression, 'a) => result<expressionValue, Reducer_ErrorValue.t>
|
||||
let evalWBindingsExpression: (expression, 'a) => result<expressionValue, Reducer_ErrorValue.t>
|
||||
let evalWBindings: (string, bindings) => Result.t<expressionValue, Reducer_ErrorValue.t>
|
||||
let eval: string => Result.t<expressionValue, Reducer_ErrorValue.t>
|
|
@ -1,5 +1,15 @@
|
|||
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
|
||||
apply e1, e2 -> apply e3 -> ... -> apply eN
|
||||
This is Lisp semantics. It holds true in both eager and lazy evaluations.
|
||||
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.
|
||||
*/
|
||||
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/def kind of statements return bindings
|
||||
and bindings = Belt.Map.String.t<expression>
|
||||
|
|
|
@ -12,17 +12,16 @@ type blockNode = {...node, "blocks": array<block>}
|
|||
//conditionalNode
|
||||
type constantNode = {...node, "value": unit}
|
||||
//functionAssignmentNode
|
||||
type functionNode = {...node, "fn": string, "args": array<node>}
|
||||
type indexNode = {...node, "dimensions": array<node>}
|
||||
type objectNode = {...node, "properties": Js.Dict.t<node>}
|
||||
type accessorNode = {...node, "object": node, "index": indexNode}
|
||||
type operatorNode = {...functionNode, "op": string}
|
||||
type accessorNode = {...node, "object": node, "index": indexNode, "name": string}
|
||||
|
||||
//parenthesisNode
|
||||
type parenthesisNode = {...node, "content": node}
|
||||
//rangeNode
|
||||
//relationalNode
|
||||
type symbolNode = {...node, "name": string}
|
||||
type functionNode = {...node, "fn": unit, "args": array<node>}
|
||||
type operatorNode = {...functionNode, "op": string}
|
||||
type assignmentNode = {...node, "object": symbolNode, "value": node}
|
||||
type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node}
|
||||
type assignmentNodeWIndex = {...assignmentNodeWAccessor, "index": Js.null<indexNode>}
|
||||
|
@ -93,6 +92,18 @@ let castNodeType = (node: node) => {
|
|||
}
|
||||
}
|
||||
|
||||
external unitAsSymbolNode: unit => symbolNode = "%identity"
|
||||
external unitAsString: unit => string = "%identity"
|
||||
|
||||
let nameOfFunctionNode = (fNode: functionNode): string => {
|
||||
let name = fNode["fn"]
|
||||
if Js.typeof(name) == "string" {
|
||||
name->unitAsString
|
||||
} else {
|
||||
(name->unitAsSymbolNode)["name"]
|
||||
}
|
||||
}
|
||||
|
||||
let rec toString = (mathJsNode: mathJsNode): string => {
|
||||
let toStringValue = (a: 'a): string =>
|
||||
if Js.typeof(a) == "string" {
|
||||
|
@ -108,7 +119,7 @@ let rec toString = (mathJsNode: mathJsNode): string => {
|
|||
->Js.String.concatMany("")
|
||||
|
||||
let toStringFunctionNode = (fnode: functionNode): string =>
|
||||
`${fnode["fn"]}(${fnode["args"]->toStringNodeArray})`
|
||||
`${fnode->nameOfFunctionNode}(${fnode["args"]->toStringNodeArray})`
|
||||
|
||||
let toStringObjectEntry = ((key: string, value: node)): string =>
|
||||
`${key}: ${value->toStringMathJsNode}`
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
module ErrorValue = Reducer_ErrorValue
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module ExtressionT = Reducer_Expression_T
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module JavaScript = Reducer_Js
|
||||
module Parse = Reducer_MathJs_Parse
|
||||
module Result = Belt.Result
|
||||
|
||||
type expression = ExtressionT.expression
|
||||
type expression = ExpressionT.expression
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
type errorValue = ErrorValue.errorValue
|
||||
|
||||
|
@ -18,10 +18,19 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
)
|
||||
)
|
||||
|
||||
let castFunctionNode = fNode => {
|
||||
let fn = fNode["fn"]->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||
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
|
||||
lispArgs->Result.map(argsCode => list{fn, ...argsCode}->ExtressionT.EList)
|
||||
passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
||||
}
|
||||
|
||||
let caseObjectNode = oNode => {
|
||||
|
@ -34,15 +43,16 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
fromNode(value)->Result.map(valueExpression => {
|
||||
let entryCode =
|
||||
list{
|
||||
key->ExpressionValue.EvString->ExtressionT.EValue,
|
||||
key->ExpressionValue.EvString->ExpressionT.EValue,
|
||||
valueExpression,
|
||||
}->ExtressionT.EList
|
||||
}->ExpressionT.EList
|
||||
list{entryCode, ...acc}
|
||||
})
|
||||
)
|
||||
)
|
||||
let lispName = "$constructRecord"->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||
rargs->Result.map(args => list{lispName, ExtressionT.EList(args)}->ExtressionT.EList)
|
||||
rargs->Result.flatMap(args =>
|
||||
passToFunction("$constructRecord", list{ExpressionT.EList(args)}->Ok)
|
||||
) // $consturctRecord gets a single argument: List of key-value paiers
|
||||
}
|
||||
|
||||
oNode["properties"]->Js.Dict.entries->Belt.List.fromArray->fromObjectEntries
|
||||
|
@ -60,30 +70,69 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
})
|
||||
),
|
||||
)
|
||||
rpropertyCodeList->Result.map(propertyCodeList => ExtressionT.EList(propertyCodeList))
|
||||
rpropertyCodeList->Result.map(propertyCodeList => ExpressionT.EList(propertyCodeList))
|
||||
}
|
||||
|
||||
let caseAccessorNode = (objectNode, indexNode) => {
|
||||
let fn = "$atIndex"->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||
|
||||
caseIndexNode(indexNode)->Result.flatMap(indexCode => {
|
||||
fromNode(objectNode)->Result.map(objectCode =>
|
||||
list{fn, objectCode, indexCode}->ExtressionT.EList
|
||||
fromNode(objectNode)->Result.flatMap(objectCode =>
|
||||
passToFunction("$atIndex", list{objectCode, indexCode}->Ok)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
switch typedMathJsNode {
|
||||
| MjArrayNode(aNode) =>
|
||||
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExtressionT.EList(list))
|
||||
| MjConstantNode(cNode) =>
|
||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.map(v => v->ExtressionT.EValue)
|
||||
| MjFunctionNode(fNode) => fNode->castFunctionNode
|
||||
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->castFunctionNode
|
||||
| MjParenthesisNode(pNode) => pNode["content"]->fromNode
|
||||
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
||||
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
||||
| MjSymbolNode(sNode) => sNode["name"]->ExpressionValue.EvSymbol->ExtressionT.EValue->Ok
|
||||
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
||||
}
|
||||
let caseAssignmentNode = aNode => {
|
||||
let symbol = aNode["object"]["name"]->toEvSymbolValue
|
||||
let rValueExpression = fromNode(aNode["value"])
|
||||
rValueExpression->Result.flatMap(valueExpression => {
|
||||
let lispArgs = list{symbol, valueExpression}->Ok
|
||||
passToFunction("$let", lispArgs)
|
||||
})
|
||||
}
|
||||
|
||||
let caseArrayNode = aNode => {
|
||||
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)
|
||||
| MjAssignmentNode(aNode) => caseAssignmentNode(aNode)
|
||||
| MjSymbolNode(sNode) => {
|
||||
let expr: expression = toEvSymbolValue(sNode["name"])
|
||||
let rExpr: result<expression, errorValue> = expr->Ok
|
||||
rExpr
|
||||
}
|
||||
| MjBlockNode(bNode) => caseBlockNode(bNode)
|
||||
// | MjBlockNode(bNode) => "statement"->toEvSymbolValue->Ok
|
||||
| MjConstantNode(cNode) =>
|
||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
||||
| MjFunctionNode(fNode) => fNode->caseFunctionNode
|
||||
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
||||
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
||||
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->caseFunctionNode
|
||||
| MjParenthesisNode(pNode) => pNode["content"]->fromNode
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
|
|
|
@ -6,19 +6,21 @@ module Extra_Array = Reducer_Extra_Array
|
|||
module ErrorValue = Reducer_ErrorValue
|
||||
|
||||
type rec expressionValue =
|
||||
| EvArray(array<expressionValue>)
|
||||
| EvBool(bool)
|
||||
| EvCall(string) // External function call
|
||||
| EvDistribution(GenericDist_Types.genericDist)
|
||||
| EvNumber(float)
|
||||
| EvRecord(Js.Dict.t<expressionValue>)
|
||||
| EvString(string)
|
||||
| EvSymbol(string)
|
||||
| EvArray(array<expressionValue>)
|
||||
| EvRecord(Js.Dict.t<expressionValue>)
|
||||
| EvDistribution(GenericDist_Types.genericDist)
|
||||
|
||||
type functionCall = (string, array<expressionValue>)
|
||||
|
||||
let rec toString = aValue =>
|
||||
switch aValue {
|
||||
| EvBool(aBool) => Js.String.make(aBool)
|
||||
| EvCall(fName) => `:${fName}`
|
||||
| EvNumber(aNumber) => Js.String.make(aNumber)
|
||||
| EvString(aString) => `'${aString}'`
|
||||
| EvSymbol(aString) => `:${aString}`
|
||||
|
@ -39,12 +41,13 @@ let rec toString = aValue =>
|
|||
->Js.String.concatMany("")
|
||||
`{${pairs}}`
|
||||
}
|
||||
| EvDistribution(dist) => `${GenericDist.toString(dist)}`
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
|
||||
let toStringWithType = aValue =>
|
||||
switch aValue {
|
||||
| EvBool(_) => `Bool::${toString(aValue)}`
|
||||
| EvCall(_) => `Call::${toString(aValue)}`
|
||||
| EvNumber(_) => `Number::${toString(aValue)}`
|
||||
| EvString(_) => `String::${toString(aValue)}`
|
||||
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||
|
|
Loading…
Reference in New Issue
Block a user