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:
Umur Ozkul 2022-04-08 14:14:37 +02:00
parent 1d550353c9
commit 660c0c70ae
12 changed files with 288 additions and 103 deletions

View File

@ -6,15 +6,18 @@ open Expect
let expectEvalToBe = (expr: string, answer: string) => let expectEvalToBe = (expr: string, answer: string) =>
Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer) Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
let testEval = (expr, answer) =>
test(expr, () => expectEvalToBe(expr, answer))
describe("builtin", () => { describe("builtin", () => {
// All MathJs operators and functions are available for string, number and boolean // All MathJs operators and functions are available for string, number and boolean
// .e.g + - / * > >= < <= == /= not and or // .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/expressions/syntax.html // See https://mathjs.org/docs/expressions/syntax.html
// See https://mathjs.org/docs/reference/functions.html // See https://mathjs.org/docs/reference/functions.html
test("-1", () => expectEvalToBe("-1", "Ok(-1)")) testEval("-1", "Ok(-1)")
test("1-1", () => expectEvalToBe("1-1", "Ok(0)")) testEval("1-1", "Ok(0)")
test("2>1", () => expectEvalToBe("2>1", "Ok(true)")) testEval("2>1", "Ok(true)")
test("concat('a','b')", () => expectEvalToBe("concat('a','b')", "Ok('ab')")) testEval("concat('a','b')", "Ok('ab')")
}) })
describe("builtin exception", () => { describe("builtin exception", () => {

View File

@ -11,9 +11,11 @@ let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)
let testDescParse = (desc, expr, answer) => test(desc, () => 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("MathJs parse", () => {
describe("literals operators paranthesis", () => { describe("literals operators paranthesis", () => {
@ -26,6 +28,10 @@ describe("MathJs parse", () => {
testParse("(1+2)", "(add(1, 2))") testParse("(1+2)", "(add(1, 2))")
}) })
describe("multi-line", () => {
testParse("1; 2", "{1; 2}")
})
describe("variables", () => { describe("variables", () => {
testParse("x = 1", "x = 1") testParse("x = 1", "x = 1")
testParse("x", "x") testParse("x", "x")
@ -33,16 +39,16 @@ describe("MathJs parse", () => {
}) })
describe("functions", () => { describe("functions", () => {
skipTestParse("identity(x) = x", "???") MySkip.testParse("identity(x) = x", "???")
skipTestParse("identity(x)", "???") MySkip.testParse("identity(x)", "???")
}) })
describe("arrays", () => { describe("arrays", () => {
test("empty", () => expectParseToBe("[]", "[]")) testDescParse("empty", "[]", "[]")
test("define", () => expectParseToBe("[0, 1, 2]", "[0, 1, 2]")) testDescParse("define", "[0, 1, 2]", "[0, 1, 2]")
test("define with strings", () => expectParseToBe("['hello', 'world']", "['hello', 'world']")) testDescParse("define with strings", "['hello', 'world']", "['hello', 'world']")
skipTestParse("range(0, 4)", "range(0, 4)") MySkip.testParse("range(0, 4)", "range(0, 4)")
test("index", () => expectParseToBe("([0,1,2])[1]", "([0, 1, 2])[1]")) testDescParse("index", "([0,1,2])[1]", "([0, 1, 2])[1]")
}) })
describe("records", () => { describe("records", () => {
@ -51,10 +57,10 @@ describe("MathJs parse", () => {
}) })
describe("comments", () => { describe("comments", () => {
skipDescTestParse("define", "# This is a comment", "???") MySkip.testDescParse("define", "# This is a comment", "???")
}) })
describe("if statement", () => { describe("if statement", () => { // TODO Tertiary operator instead
skipDescTestParse("define", "if (true) { 1 } else { 0 }", "???") MySkip.testDescParse("define", "if (true) { 1 } else { 0 }", "???")
}) })
}) })

View File

@ -42,6 +42,15 @@ describe("reducer using mathjs parse", () => {
"Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))", "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", () => { describe("eval", () => {
@ -71,13 +80,26 @@ describe("eval", () => {
test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)")) test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)"))
test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)")) 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", () => { describe("test exceptions", () => {
testDescEvalToBe( testDescEvalToBe(
"javascript exception", "javascript exception",
"jsraise('div by 0')", "javascriptraise('div by 0')",
"Error(JS Exception: Error: '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)")
}) })

View File

@ -12,6 +12,7 @@
"test:watch": "jest --watchAll", "test:watch": "jest --watchAll",
"coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html", "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 {}' \\;", "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" "all": "yarn build && yarn bundle && yarn test"
}, },
"keywords": [ "keywords": [

View File

@ -14,8 +14,8 @@ exception TestRescriptException
let callInternal = (call: functionCall): result<'b, errorValue> => { let callInternal = (call: functionCall): result<'b, errorValue> => {
let callMathJs = (call: functionCall): result<'b, errorValue> => let callMathJs = (call: functionCall): result<'b, errorValue> =>
switch call { switch call {
| ("jsraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests | ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests
| ("resraise", _) => raise(TestRescriptException) // For Tests | ("rescriptraise", _) => raise(TestRescriptException) // For Tests
| call => call->toStringFunctionCall->MathJs.Eval.eval | call => call->toStringFunctionCall->MathJs.Eval.eval
} }

View File

@ -1,8 +1,12 @@
type errorValue = type errorValue =
| REArrayIndexNotFound(string, int) | REArrayIndexNotFound(string, int)
| REAssignmentExpected
| REExpressionExpected
| REFunctionExpected(string) | REFunctionExpected(string)
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception | REJavaScriptExn(option<string>, option<string>) // Javascript Exception
| REMacroNotFound(string)
| RERecordPropertyNotFound(string, string) | RERecordPropertyNotFound(string, string)
| RESymbolNotFound(string)
| RESyntaxError(string) | RESyntaxError(string)
| RETodo(string) // To do | RETodo(string) // To do
@ -11,6 +15,8 @@ type t = errorValue
let errorToString = err => let errorToString = err =>
switch err { switch err {
| REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}`
| REAssignmentExpected => "Assignment expected"
| REExpressionExpected => "Expression expected"
| REFunctionExpected(msg) => `Function expected: ${msg}` | REFunctionExpected(msg) => `Function expected: ${msg}`
| REJavaScriptExn(omsg, oname) => { | REJavaScriptExn(omsg, oname) => {
let answer = "JS Exception:" let answer = "JS Exception:"
@ -24,7 +30,9 @@ let errorToString = err =>
} }
answer answer
} }
| REMacroNotFound(macro) => `Macro not found: ${macro}`
| RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}`
| RESymbolNotFound(symbolName) => `${symbolName} is not defined`
| RESyntaxError(desc) => `Syntax Error: ${desc}` | RESyntaxError(desc) => `Syntax Error: ${desc}`
| RETodo(msg) => `TODO: ${msg}` | RETodo(msg) => `TODO: ${msg}`
} }

View File

@ -15,6 +15,7 @@ type t = expression
*/ */
let rec toString = expression => let rec toString = expression =>
switch expression { switch expression {
| T.EBindings(bindings) => "$$bound"
| T.EList(aList) => | T.EList(aList) =>
`(${Belt.List.map(aList, aValue => toString(aValue)) `(${Belt.List.map(aList, aValue => toString(aValue))
->Extra.List.interperse(" ") ->Extra.List.interperse(" ")
@ -38,24 +39,105 @@ let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
let parse = (mathJsCode: string): result<t, errorValue> => let parse = (mathJsCode: string): result<t, errorValue> =>
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode) mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
module MapString = Belt.Map.String let defaultBindings: T.bindings = Belt.Map.String.empty
type bindings = MapString.t<unit>
let defaultBindings: bindings = MapString.fromArray([])
// TODO Define bindings for function execution context
/* /*
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 { 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 | _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
} }
/* /*
Recursively evaluate/reduce the code tree Macros are like functions but instead of taking values as parameters,
*/ they take expressions as parameters and return a new expression.
let rec reduceExpression = (expression: t, bindings): result<expressionValue, 'e> => 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 { switch expression {
| T.EValue(value) => value->Ok | T.EValue(value) => value->Ok
| T.EList(list) => { | T.EList(list) => {
@ -65,7 +147,7 @@ let rec reduceExpression = (expression: t, bindings): result<expressionValue, 'e
) => ) =>
racc->Result.flatMap(acc => { racc->Result.flatMap(acc => {
each each
->reduceExpression(bindings) ->reduceExpandedExpression
->Result.flatMap(newNode => { ->Result.flatMap(newNode => {
acc->Belt.List.add(newNode)->Ok 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> => let evalWBindingsExpression = (aExpression, bindings): result<expressionValue, 'e> =>
reduceExpression(aExpression, bindings) reduceExpression(aExpression, bindings)
/* /*
Evaluates MathJs code via Lisp using bindings and answers the result 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)) parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings))
} }

View File

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

View File

@ -1,5 +1,15 @@
open ReducerInterface.ExpressionValue 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 = type rec expression =
| EList(list<expression>) // A list to map-reduce | EList(list<expression>) // A list to map-reduce
| EValue(expressionValue) // Irreducible built-in value. Reducer should not know the internals. External libraries are responsible | 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>

View File

@ -12,17 +12,16 @@ type blockNode = {...node, "blocks": array<block>}
//conditionalNode //conditionalNode
type constantNode = {...node, "value": unit} type constantNode = {...node, "value": unit}
//functionAssignmentNode //functionAssignmentNode
type functionNode = {...node, "fn": string, "args": array<node>}
type indexNode = {...node, "dimensions": array<node>} type indexNode = {...node, "dimensions": array<node>}
type objectNode = {...node, "properties": Js.Dict.t<node>} type objectNode = {...node, "properties": Js.Dict.t<node>}
type accessorNode = {...node, "object": node, "index": indexNode} type accessorNode = {...node, "object": node, "index": indexNode, "name": string}
type operatorNode = {...functionNode, "op": string}
//parenthesisNode
type parenthesisNode = {...node, "content": node} type parenthesisNode = {...node, "content": node}
//rangeNode //rangeNode
//relationalNode //relationalNode
type symbolNode = {...node, "name": string} 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 assignmentNode = {...node, "object": symbolNode, "value": node}
type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node} type assignmentNodeWAccessor = {...node, "object": accessorNode, "value": node}
type assignmentNodeWIndex = {...assignmentNodeWAccessor, "index": Js.null<indexNode>} 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 rec toString = (mathJsNode: mathJsNode): string => {
let toStringValue = (a: 'a): string => let toStringValue = (a: 'a): string =>
if Js.typeof(a) == "string" { if Js.typeof(a) == "string" {
@ -108,7 +119,7 @@ let rec toString = (mathJsNode: mathJsNode): string => {
->Js.String.concatMany("") ->Js.String.concatMany("")
let toStringFunctionNode = (fnode: functionNode): string => let toStringFunctionNode = (fnode: functionNode): string =>
`${fnode["fn"]}(${fnode["args"]->toStringNodeArray})` `${fnode->nameOfFunctionNode}(${fnode["args"]->toStringNodeArray})`
let toStringObjectEntry = ((key: string, value: node)): string => let toStringObjectEntry = ((key: string, value: node)): string =>
`${key}: ${value->toStringMathJsNode}` `${key}: ${value->toStringMathJsNode}`

View File

@ -1,11 +1,11 @@
module ErrorValue = Reducer_ErrorValue module ErrorValue = Reducer_ErrorValue
module ExpressionValue = ReducerInterface.ExpressionValue module ExpressionValue = ReducerInterface.ExpressionValue
module ExtressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module JavaScript = Reducer_Js module JavaScript = Reducer_Js
module Parse = Reducer_MathJs_Parse module Parse = Reducer_MathJs_Parse
module Result = Belt.Result module Result = Belt.Result
type expression = ExtressionT.expression type expression = ExpressionT.expression
type expressionValue = ExpressionValue.expressionValue type expressionValue = ExpressionValue.expressionValue
type errorValue = ErrorValue.errorValue type errorValue = ErrorValue.errorValue
@ -18,10 +18,19 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
) )
) )
let castFunctionNode = fNode => { let toEvCallValue = (name: string): expression =>
let fn = fNode["fn"]->ExpressionValue.EvSymbol->ExtressionT.EValue 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 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 => { let caseObjectNode = oNode => {
@ -34,15 +43,16 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
fromNode(value)->Result.map(valueExpression => { fromNode(value)->Result.map(valueExpression => {
let entryCode = let entryCode =
list{ list{
key->ExpressionValue.EvString->ExtressionT.EValue, key->ExpressionValue.EvString->ExpressionT.EValue,
valueExpression, valueExpression,
}->ExtressionT.EList }->ExpressionT.EList
list{entryCode, ...acc} list{entryCode, ...acc}
}) })
) )
) )
let lispName = "$constructRecord"->ExpressionValue.EvSymbol->ExtressionT.EValue rargs->Result.flatMap(args =>
rargs->Result.map(args => list{lispName, ExtressionT.EList(args)}->ExtressionT.EList) 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 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 caseAccessorNode = (objectNode, indexNode) => {
let fn = "$atIndex"->ExpressionValue.EvSymbol->ExtressionT.EValue
caseIndexNode(indexNode)->Result.flatMap(indexCode => { caseIndexNode(indexNode)->Result.flatMap(indexCode => {
fromNode(objectNode)->Result.map(objectCode => fromNode(objectNode)->Result.flatMap(objectCode =>
list{fn, objectCode, indexCode}->ExtressionT.EList passToFunction("$atIndex", list{objectCode, indexCode}->Ok)
) )
}) })
} }
switch typedMathJsNode { let caseAssignmentNode = aNode => {
| MjArrayNode(aNode) => let symbol = aNode["object"]["name"]->toEvSymbolValue
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExtressionT.EList(list)) let rValueExpression = fromNode(aNode["value"])
| MjConstantNode(cNode) => rValueExpression->Result.flatMap(valueExpression => {
cNode["value"]->JavaScript.Gate.jsToEv->Result.map(v => v->ExtressionT.EValue) let lispArgs = list{symbol, valueExpression}->Ok
| MjFunctionNode(fNode) => fNode->castFunctionNode passToFunction("$let", lispArgs)
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->castFunctionNode })
| MjParenthesisNode(pNode) => pNode["content"]->fromNode }
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
| MjObjectNode(oNode) => caseObjectNode(oNode) let caseArrayNode = aNode => {
| MjSymbolNode(sNode) => sNode["name"]->ExpressionValue.EvSymbol->ExtressionT.EValue->Ok aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list))
| MjIndexNode(iNode) => caseIndexNode(iNode) }
}
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
}) })

View File

@ -6,19 +6,21 @@ module Extra_Array = Reducer_Extra_Array
module ErrorValue = Reducer_ErrorValue module ErrorValue = Reducer_ErrorValue
type rec expressionValue = type rec expressionValue =
| EvArray(array<expressionValue>)
| EvBool(bool) | EvBool(bool)
| EvCall(string) // External function call
| EvDistribution(GenericDist_Types.genericDist)
| EvNumber(float) | EvNumber(float)
| EvRecord(Js.Dict.t<expressionValue>)
| EvString(string) | EvString(string)
| EvSymbol(string) | EvSymbol(string)
| EvArray(array<expressionValue>)
| EvRecord(Js.Dict.t<expressionValue>)
| EvDistribution(GenericDist_Types.genericDist)
type functionCall = (string, array<expressionValue>) type functionCall = (string, array<expressionValue>)
let rec toString = aValue => let rec toString = aValue =>
switch aValue { switch aValue {
| EvBool(aBool) => Js.String.make(aBool) | EvBool(aBool) => Js.String.make(aBool)
| EvCall(fName) => `:${fName}`
| EvNumber(aNumber) => Js.String.make(aNumber) | EvNumber(aNumber) => Js.String.make(aNumber)
| EvString(aString) => `'${aString}'` | EvString(aString) => `'${aString}'`
| EvSymbol(aString) => `:${aString}` | EvSymbol(aString) => `:${aString}`
@ -39,12 +41,13 @@ let rec toString = aValue =>
->Js.String.concatMany("") ->Js.String.concatMany("")
`{${pairs}}` `{${pairs}}`
} }
| EvDistribution(dist) => `${GenericDist.toString(dist)}` | EvDistribution(dist) => GenericDist.toString(dist)
} }
let toStringWithType = aValue => let toStringWithType = aValue =>
switch aValue { switch aValue {
| EvBool(_) => `Bool::${toString(aValue)}` | EvBool(_) => `Bool::${toString(aValue)}`
| EvCall(_) => `Call::${toString(aValue)}`
| EvNumber(_) => `Number::${toString(aValue)}` | EvNumber(_) => `Number::${toString(aValue)}`
| EvString(_) => `String::${toString(aValue)}` | EvString(_) => `String::${toString(aValue)}`
| EvSymbol(_) => `Symbol::${toString(aValue)}` | EvSymbol(_) => `Symbol::${toString(aValue)}`