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) =>
|
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", () => {
|
||||||
|
|
|
@ -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 }", "???")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)")
|
||||||
})
|
})
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
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>
|
||||||
|
|
|
@ -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}`
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)}`
|
||||||
|
|
Loading…
Reference in New Issue
Block a user