tests; drop mathjs; new FR helpers; disable type tests
This commit is contained in:
parent
271303fb5f
commit
f8b743feb5
|
@ -1,147 +0,0 @@
|
|||
// 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 exampleStatementY = eLetStatement("y", eNumber(1.))
|
||||
// let exampleStatementX = eLetStatement("y", eSymbol("x"))
|
||||
// let exampleStatementZ = eLetStatement("z", eSymbol("y"))
|
||||
|
||||
// // If it is not a macro 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([]), exampleStatementY),
|
||||
// "Ok((:$_setBindings_$ @{} :y 1) context: @{})",
|
||||
// )
|
||||
// // Then it answers the bindings for the next statement when reduced
|
||||
// testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok(@{y: 1})")
|
||||
// // Now let's feed a binding to see what happens
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBindStatement(eBindings([("x", IEvNumber(2.))]), exampleStatementX),
|
||||
// "Ok((:$_setBindings_$ @{x: 2} :y 2) context: @{x: 2})",
|
||||
// )
|
||||
// // An expression does not return a binding, thus error
|
||||
// testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
|
||||
// // When bindings from previous statement are missing the context is injected. This must be the first statement of a block
|
||||
// testMacro(
|
||||
// [("z", IEvNumber(99.))],
|
||||
// eBindStatementDefault(exampleStatementY),
|
||||
// "Ok((:$_setBindings_$ @{z: 99} :y 1) context: @{z: 99})",
|
||||
// )
|
||||
// })
|
||||
|
||||
// describe("bindExpression", () => {
|
||||
// // x is simply bound in the expression
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBindExpression(eBindings([("x", IEvNumber(2.))]), eSymbol("x")),
|
||||
// "Ok(2 context: @{x: 2})",
|
||||
// )
|
||||
// // When an let statement is the end expression then bindings are returned
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
|
||||
// "Ok((:$_exportBindings_$ (:$_setBindings_$ @{x: 2} :y 1)) context: @{x: 2})",
|
||||
// )
|
||||
// // Now let's reduce that expression
|
||||
// testMacroEval(
|
||||
// [],
|
||||
// eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
|
||||
// "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", IEvNumber(99.))],
|
||||
// eBindExpressionDefault(exampleStatementY),
|
||||
// "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{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))")
|
||||
// testMacroEval([], eBlock(list{exampleStatementY}), "Ok(@{y: 1})")
|
||||
// // Block with a statement and an expression
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBlock(list{exampleStatementY, exampleExpressionY}),
|
||||
// "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))",
|
||||
// )
|
||||
// testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
|
||||
// // Block with a statement and another statement
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBlock(list{exampleStatementY, exampleStatementZ}),
|
||||
// "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))",
|
||||
// )
|
||||
// testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok(@{y: 1,z: 1})")
|
||||
// // Block inside a block
|
||||
// testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {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 {{: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
|
||||
// // :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)"
|
||||
// testMacro(
|
||||
// [],
|
||||
// eBlock(list{
|
||||
// eBlock(list{
|
||||
// eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
|
||||
// eSymbol("y"),
|
||||
// }),
|
||||
// }),
|
||||
// "Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))",
|
||||
// )
|
||||
// testMacroEval(
|
||||
// [("x", IEvNumber(1.))],
|
||||
// eBlock(list{
|
||||
// eBlock(list{
|
||||
// eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
|
||||
// eSymbol("y"),
|
||||
// }),
|
||||
// }),
|
||||
// "Ok(2)",
|
||||
// )
|
||||
// })
|
||||
|
||||
// describe("lambda", () => {
|
||||
// // assign a lambda to a variable
|
||||
// let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY})
|
||||
// testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
|
||||
// // 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", IEvNumber(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", IEvNumber(666.))], callLambdaExpression, "Ok(667)")
|
||||
// })
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
module ExpressionValue = ReducerInterface.InternalExpressionValue
|
||||
module Expression = Reducer_Expression
|
||||
|
||||
open Jest
|
||||
open Expect
|
||||
|
||||
let expectEvalToBe = (sourceCode: string, answer: string) =>
|
||||
Expression.BackCompatible.evaluateString(sourceCode)
|
||||
->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
|
||||
testEval("-1", "Ok(-1)")
|
||||
testEval("1-1", "Ok(0)")
|
||||
testEval("2>1", "Ok(true)")
|
||||
testEval("concat('a','b')", "Ok('ab')")
|
||||
})
|
||||
|
||||
describe("builtin exception", () => {
|
||||
//MathJS is no more, this is just a normal function call (TODO - refactor tests)
|
||||
test("MathJs Exception", () =>
|
||||
expectEvalToBe("testZadanga(1)", "Error(testZadanga is not defined)")
|
||||
)
|
||||
})
|
||||
|
||||
describe("error reporting from collection functions", () => {
|
||||
testEval("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
|
||||
testEval(
|
||||
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
|
||||
"Error(zarathsuzaWasHere is not defined)",
|
||||
)
|
||||
// FIXME: returns "Error(Function not found: map(Array,Symbol))"
|
||||
// Actually this error is correct but not informative
|
||||
})
|
|
@ -1,31 +0,0 @@
|
|||
module MathJs = Reducer_MathJs
|
||||
module ErrorValue = Reducer_ErrorValue
|
||||
|
||||
open Jest
|
||||
open ExpectJs
|
||||
|
||||
describe("eval", () => {
|
||||
test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(IEvNumber(1.))))
|
||||
test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(IEvNumber(0.))))
|
||||
test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(IEvString("hello"))))
|
||||
test("String expr", () =>
|
||||
expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(IEvString("hello world")))
|
||||
)
|
||||
test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(IEvBool(true))))
|
||||
test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(IEvBool(true))))
|
||||
})
|
||||
|
||||
describe("errors", () => {
|
||||
// All those errors propagete up and are returned by the resolver
|
||||
test("unknown function", () =>
|
||||
expect(MathJs.Eval.eval("testZadanga()"))->toEqual(
|
||||
Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))),
|
||||
)
|
||||
)
|
||||
|
||||
test("unknown answer type", () =>
|
||||
expect(MathJs.Eval.eval("1+1i"))->toEqual(
|
||||
Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")),
|
||||
)
|
||||
)
|
||||
})
|
|
@ -3,22 +3,22 @@ open Reducer_Peggy_TestHelpers
|
|||
|
||||
describe("Peggy void", () => {
|
||||
//literal
|
||||
testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ())
|
||||
testToExpression("()", "()", ~v="()", ())
|
||||
testToExpression(
|
||||
"fn()=1",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}",
|
||||
"fn = {|_| {1}}",
|
||||
// ~v="@{fn: lambda(_=>internal code)}",
|
||||
(),
|
||||
)
|
||||
testToExpression(
|
||||
"fn()=1; fn()",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () (:fn ()))}",
|
||||
"fn = {|_| {1}}; (fn)(())",
|
||||
~v="1",
|
||||
(),
|
||||
)
|
||||
testToExpression(
|
||||
"fn(a)=(); call fn(1)",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}",
|
||||
"fn = {|a| {()}}; _ = {(fn)(1)}",
|
||||
// ~v="@{_: (),fn: lambda(a=>internal code)}",
|
||||
(),
|
||||
)
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
// open Jest
|
||||
// open Expect
|
||||
|
||||
// module BindingsReplacer = Reducer_Expression_BindingsReplacer
|
||||
// module Expression = Reducer_Expression
|
||||
// module ExpressionWithContext = Reducer_ExpressionWithContext
|
||||
// module InternalExpressionValue = ReducerInterface.InternalExpressionValue
|
||||
// module Macro = Reducer_Expression_Macro
|
||||
// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
|
||||
// module T = Reducer_Expression_T
|
||||
|
||||
// let testMacro_ = (
|
||||
// tester,
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedCode: string,
|
||||
// ) => {
|
||||
// let bindings = Bindings.fromArray(bindArray)
|
||||
// tester(expr->T.toString, () =>
|
||||
// expr
|
||||
// ->Macro.expandMacroCallRs(
|
||||
// bindings,
|
||||
// ProjectAccessorsT.identityAccessors,
|
||||
// Expression.reduceExpressionInProject,
|
||||
// )
|
||||
// ->ExpressionWithContext.toStringResult
|
||||
// ->expect
|
||||
// ->toEqual(expectedCode)
|
||||
// )
|
||||
// }
|
||||
|
||||
// let testMacroEval_ = (
|
||||
// tester,
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedValue: string,
|
||||
// ) => {
|
||||
// let bindings = Reducer_Bindings.fromArray(bindArray)
|
||||
// tester(expr->T.toString, () =>
|
||||
// expr
|
||||
// ->Macro.doMacroCall(
|
||||
// bindings,
|
||||
// ProjectAccessorsT.identityAccessors,
|
||||
// Expression.reduceExpressionInProject,
|
||||
// )
|
||||
// ->Ok
|
||||
// ->InternalExpressionValue.toStringResult
|
||||
// ->expect
|
||||
// ->toEqual(expectedValue)
|
||||
// )
|
||||
// }
|
||||
|
||||
// let testMacro = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedExpr: string,
|
||||
// ) => testMacro_(test, bindArray, expr, expectedExpr)
|
||||
// let testMacroEval = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedValue: string,
|
||||
// ) => testMacroEval_(test, bindArray, expr, expectedValue)
|
||||
|
||||
// module MySkip = {
|
||||
// let testMacro = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedExpr: string,
|
||||
// ) => testMacro_(Skip.test, bindArray, expr, expectedExpr)
|
||||
// let testMacroEval = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedValue: string,
|
||||
// ) => testMacroEval_(Skip.test, bindArray, expr, expectedValue)
|
||||
// }
|
||||
|
||||
// module MyOnly = {
|
||||
// let testMacro = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedExpr: string,
|
||||
// ) => testMacro_(Only.test, bindArray, expr, expectedExpr)
|
||||
// let testMacroEval = (
|
||||
// bindArray: array<(string, InternalExpressionValue.t)>,
|
||||
// expr: T.expression,
|
||||
// expectedValue: string,
|
||||
// ) => testMacroEval_(Only.test, bindArray, expr, expectedValue)
|
||||
// }
|
||||
|
|
@ -10,3 +10,7 @@ describe("Parse function assignment", () => {
|
|||
describe("Evaluate function assignment", () => {
|
||||
testEvalToBe("f(x)=x; f(1)", "Ok(1)")
|
||||
})
|
||||
|
||||
describe("Shadowing", () => {
|
||||
testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)")
|
||||
})
|
||||
|
|
|
@ -5,3 +5,7 @@ Skip.describe("map reduce (sam)", () => {
|
|||
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
|
||||
testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???")
|
||||
})
|
||||
|
||||
describe("map", () => {
|
||||
testEvalToBe("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
|
||||
})
|
||||
|
|
|
@ -2,20 +2,29 @@ open Jest
|
|||
open Reducer_TestHelpers
|
||||
|
||||
describe("eval", () => {
|
||||
// All MathJs operators and functions are builtin for string, float and boolean
|
||||
// .e.g + - / * > >= < <= == /= not and or
|
||||
// See https://mathjs.org/docs/reference/functions.html
|
||||
describe("expressions", () => {
|
||||
testEvalToBe("1", "Ok(1)")
|
||||
testEvalToBe("-1", "Ok(-1)")
|
||||
testEvalToBe("1-1", "Ok(0)")
|
||||
testEvalToBe("1+2", "Ok(3)")
|
||||
testEvalToBe("(1+2)*3", "Ok(9)")
|
||||
testEvalToBe("2>1", "Ok(true)")
|
||||
testEvalToBe("concat('a ', 'b')", "Ok('a b')")
|
||||
testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])")
|
||||
testEvalToBe("log(10)", "Ok(2.302585092994046)")
|
||||
testEvalToBe("cos(10)", "Ok(-0.8390715290764524)")
|
||||
testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)")
|
||||
// TODO more built ins
|
||||
})
|
||||
|
||||
describe("missing function", () => {
|
||||
testEvalToBe("testZadanga(1)", "Error(testZadanga is not defined)")
|
||||
|
||||
testEvalToBe(
|
||||
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
|
||||
"Error(zarathsuzaWasHere is not defined)",
|
||||
)
|
||||
})
|
||||
|
||||
describe("arrays", () => {
|
||||
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
|
||||
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
|
||||
|
@ -51,6 +60,10 @@ describe("eval", () => {
|
|||
testEvalError("1; 1")
|
||||
testEvalToBe("x=1; x=1; x", "Ok(1)")
|
||||
})
|
||||
|
||||
describe("blocks", () => {
|
||||
testEvalToBe("x = { y = { z = 5; z * 2 }; y + 3 }; x", "Ok(13)")
|
||||
})
|
||||
})
|
||||
|
||||
describe("test exceptions", () => {
|
||||
|
|
|
@ -10,5 +10,7 @@ module.exports = {
|
|||
"/node_modules/",
|
||||
".*Helpers.bs.js",
|
||||
".*Helpers.ts",
|
||||
".*Reducer_Type.*",
|
||||
".*_type_test.bs.js",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -25,52 +25,87 @@ let makeFn = (
|
|||
fn: array<internalExpressionValue> => result<internalExpressionValue, errorValue>,
|
||||
) => makeFnMany(name, [{inputs: inputs, fn: fn}])
|
||||
|
||||
let makeFF2F = (name: string, fn: (float, float) => float) => {
|
||||
makeFn(name, [FRTypeNumber, FRTypeNumber], inputs => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvNumber->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let makeFF2B = (name: string, fn: (float, float) => bool) => {
|
||||
makeFn(name, [FRTypeNumber, FRTypeNumber], inputs => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvBool->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let makeBB2B = (name: string, fn: (bool, bool) => bool) => {
|
||||
makeFn(name, [FRTypeBool, FRTypeBool], inputs => {
|
||||
switch inputs {
|
||||
| [IEvBool(x), IEvBool(y)] => fn(x, y)->IEvBool->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let library = [
|
||||
makeFF2F("add", (x, y) => x +. y), // infix + (see Reducer/Reducer_Peggy/helpers.ts)
|
||||
makeFF2F("subtract", (x, y) => x -. y), // infix -
|
||||
makeFF2F("multiply", (x, y) => x *. y), // infix *
|
||||
makeFF2F("divide", (x, y) => x /. y), // infix /
|
||||
makeFF2F("pow", (x, y) => Js.Math.pow_float(~base=x, ~exp=y)), // infix ^
|
||||
makeFF2B("equal", (x, y) => x == y), // infix ==
|
||||
makeFF2B("smaller", (x, y) => x < y), // infix <
|
||||
makeFF2B("smallerEq", (x, y) => x <= y), // infix <=
|
||||
makeFF2B("larger", (x, y) => x > y), // infix >
|
||||
makeFF2B("largerEq", (x, y) => x >= y), // infix >=
|
||||
makeBB2B("or", (x, y) => x || y), // infix ||
|
||||
makeBB2B("and", (x, y) => x && y), // infix &&
|
||||
makeFn("unaryMinus", [FRTypeNumber], inputs => { // unary prefix -
|
||||
switch inputs {
|
||||
| [IEvNumber(x)] => IEvNumber(-.x)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}),
|
||||
Make.ff2f(
|
||||
~name="add", // infix + (see Reducer/Reducer_Peggy/helpers.ts)
|
||||
~fn=(x, y) => x +. y,
|
||||
()
|
||||
),
|
||||
Make.ff2f(
|
||||
~name="subtract", // infix -
|
||||
~fn=(x, y) => x -. y,
|
||||
()
|
||||
),
|
||||
Make.ff2f(
|
||||
~name="multiply", // infix *
|
||||
~fn=(x, y) => x *. y,
|
||||
()
|
||||
),
|
||||
Make.ff2f(
|
||||
~name="divide", // infix /
|
||||
~fn=(x, y) => x /. y,
|
||||
()
|
||||
),
|
||||
Make.ff2f(
|
||||
~name="pow", // infix ^
|
||||
~fn=(x, y) => Js.Math.pow_float(~base=x, ~exp=y),
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="equal", // infix == on numbers
|
||||
~fn=(x, y) => x == y,
|
||||
()
|
||||
),
|
||||
Make.bb2b(
|
||||
~name="equal", // infix == on booleans
|
||||
~fn=(x, y) => x == y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="unequal", // infix != on numbers
|
||||
~fn=(x, y) => x != y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="unequal", // infix != on booleans
|
||||
~fn=(x, y) => x != y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="smaller", // infix <
|
||||
~fn=(x, y) => x < y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="smallerEq", // infix <=
|
||||
~fn=(x, y) => x <= y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="larger", // infix >
|
||||
~fn=(x, y) => x > y,
|
||||
()
|
||||
),
|
||||
Make.ff2b(
|
||||
~name="largerEq", // infix >=
|
||||
~fn=(x, y) => x >= y,
|
||||
()
|
||||
),
|
||||
Make.bb2b(
|
||||
~name="or", // infix ||
|
||||
~fn=(x, y) => x || y,
|
||||
()
|
||||
),
|
||||
Make.bb2b(
|
||||
~name="and", // infix &&
|
||||
~fn=(x, y) => x && y,
|
||||
()
|
||||
),
|
||||
Make.f2f(
|
||||
~name="unaryMinus", // unary prefix -
|
||||
~fn=x => -.x,
|
||||
()
|
||||
),
|
||||
makeFn("not", [FRTypeNumber], inputs => { // unary prefix !
|
||||
switch inputs {
|
||||
| [IEvNumber(x)] => IEvBool(x != 0.)->Ok
|
||||
|
@ -120,4 +155,12 @@ let library = [
|
|||
| _ => Error(impossibleError)
|
||||
}
|
||||
}),
|
||||
makeFn("javascriptraise", [FRTypeAny], inputs => {
|
||||
switch inputs {
|
||||
| [msg] => {
|
||||
Js.Exn.raiseError(msg->ReducerInterface_InternalExpressionValue.toString)
|
||||
}
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
|
58
packages/squiggle-lang/src/rescript/FR/FR_Math.res
Normal file
58
packages/squiggle-lang/src/rescript/FR/FR_Math.res
Normal file
|
@ -0,0 +1,58 @@
|
|||
open FunctionRegistry_Helpers
|
||||
|
||||
let library = [
|
||||
// ported MathJS functions
|
||||
// https://docs.google.com/spreadsheets/d/1bUK2RaBFg8aJHuzZcw9yXp8StCBH5If5sU2iRw1T_HY/edit
|
||||
// TODO - implement the rest of useful stuff
|
||||
|
||||
Make.f2f(
|
||||
~name="sqrt",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=(x) => Js.Math.pow_float(~base=x, ~exp=0.5),
|
||||
(),
|
||||
),
|
||||
|
||||
Make.f2f(
|
||||
~name="sin",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.sin,
|
||||
(),
|
||||
),
|
||||
Make.f2f(
|
||||
~name="cos",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.cos,
|
||||
(),
|
||||
),
|
||||
Make.f2f(
|
||||
~name="tan",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.tan,
|
||||
(),
|
||||
),
|
||||
Make.f2f(
|
||||
~name="asin",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.asin,
|
||||
(),
|
||||
),
|
||||
Make.f2f(
|
||||
~name="acos",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.acos,
|
||||
(),
|
||||
),
|
||||
Make.f2f(
|
||||
~name="atan",
|
||||
~nameSpace="Math",
|
||||
~requiresNamespace=true,
|
||||
~fn=Js.Math.atan,
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -1,70 +0,0 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
// FIXME - copy-pasted (see FR_Date.res and others)
|
||||
let makeFn = (
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
fn: array<internalExpressionValue> => result<internalExpressionValue, errorValue>,
|
||||
) =>
|
||||
Function.make(
|
||||
~name,
|
||||
~nameSpace="",
|
||||
~requiresNamespace=false,
|
||||
~definitions=[FnDefinition.make(~name, ~inputs, ~run=(inputs, _, _, _) => fn(inputs), ())],
|
||||
(),
|
||||
)
|
||||
|
||||
@module("mathjs") external dummy_: string => unit = "evaluate"
|
||||
let dummy1_ = dummy_ //Deceive the compiler to make the import although we wont make a call from rescript. Otherwise the optimizer deletes the import
|
||||
|
||||
let mathjsCall1: (string, float) => 'a = %raw(`function (name, arg) { return Mathjs[name](arg); }`)
|
||||
let mathjsCall2: (string, float, float) => 'a = %raw(`function (name, arg1, arg2) { return Mathjs[name](arg1, arg2); }`)
|
||||
|
||||
let makeMathjsFn1 = (
|
||||
name: string
|
||||
) => {
|
||||
makeFn(name, [FRTypeNumber], inputs => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x)] => mathjsCall1(name, x)->Reducer_Js_Gate.jsToIEv
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let makeMathjsFn2 = (
|
||||
name: string
|
||||
) => {
|
||||
makeFn(name, [FRTypeNumber, FRTypeNumber], inputs => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => mathjsCall2(name, x, y)->Reducer_Js_Gate.jsToIEv
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let library = [
|
||||
// TODO - other MathJS
|
||||
// https://mathjs.org/docs/reference/functions.html
|
||||
|
||||
// Arithmetic functions
|
||||
makeMathjsFn1("abs"),
|
||||
makeMathjsFn1("cbrt"),
|
||||
makeMathjsFn1("ceil"),
|
||||
makeMathjsFn1("cube"),
|
||||
makeMathjsFn1("exp"),
|
||||
makeMathjsFn1("fix"),
|
||||
makeMathjsFn1("floor"),
|
||||
|
||||
makeMathjsFn2("gcd"),
|
||||
makeMathjsFn2("hypot"),
|
||||
makeMathjsFn2("invmod"),
|
||||
makeMathjsFn2("lcm"),
|
||||
makeMathjsFn1("log"), // Do we need makeMathjsFn2 for `log` too?
|
||||
makeMathjsFn1("log10"),
|
||||
makeMathjsFn1("log2"),
|
||||
|
||||
makeMathjsFn1("factorial"),
|
||||
makeMathjsFn1("cos"),
|
||||
|
||||
]
|
|
@ -20,13 +20,12 @@ module ArrayNumberDist = {
|
|||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
Make.f2f(
|
||||
~name="floor",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`floor(3.5)`],
|
||||
~definitions=[DefineFn.Numbers.oneToOne("floor", Js.Math.floor_float)],
|
||||
~fn=Js.Math.floor_float,
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
|
|
|
@ -259,3 +259,143 @@ module DefineFn = {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
module Make = {
|
||||
/*
|
||||
Opinionated explanations for API choices here:
|
||||
|
||||
Q: Why such short names?
|
||||
A: Because we have to type them a lot in definitions.
|
||||
|
||||
Q: Why not DefineFn.Numbers.oneToOne / DefineFn.Numbers.twoToOne / ...?
|
||||
A: Because return values matter too, and we have many possible combinations: numbers to numbers, pairs of numbers to numbers, pair of numbers to bools.
|
||||
|
||||
Q: Does this approach scale?
|
||||
A: It's good enough for most cases, and we can fall back on raw `Function.make` if necessary. We should figure out the better API powered by parameterized types, but it's hard (and might require PPX).
|
||||
|
||||
Q: What about `frValue` types?
|
||||
A: I hope we'll get rid of them soon.
|
||||
|
||||
Q: What about polymorphic functions with multiple definitions? Why ~fn is not an array?
|
||||
A: We often define the same function in multiple `FR_*` files, so that doesn't work well anyway. In 90%+ cases there's a single definition. And having to write `name` twice is annoying.
|
||||
*/
|
||||
let f2f = (
|
||||
~name: string,
|
||||
~fn: (float) => float,
|
||||
~nameSpace="",
|
||||
~requiresNamespace=false,
|
||||
~examples=?,
|
||||
(),
|
||||
) => {
|
||||
Function.make(
|
||||
~name,
|
||||
~nameSpace,
|
||||
~requiresNamespace=requiresNamespace,
|
||||
~examples=examples->E.O.default([], _),
|
||||
~output=EvtNumber,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeNumber],
|
||||
~run=((inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvNumber(x)] => fn(x)->IEvNumber->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}),
|
||||
()
|
||||
)
|
||||
],
|
||||
()
|
||||
)
|
||||
}
|
||||
|
||||
let ff2f = (
|
||||
~name: string,
|
||||
~fn: (float, float) => float,
|
||||
~nameSpace="",
|
||||
~requiresNamespace=false,
|
||||
~examples=?,
|
||||
(),
|
||||
) => {
|
||||
Function.make(
|
||||
~name,
|
||||
~nameSpace,
|
||||
~requiresNamespace=requiresNamespace,
|
||||
~examples=examples->E.O.default([], _),
|
||||
~output=EvtNumber,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=((inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvNumber->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}),
|
||||
()
|
||||
)
|
||||
],
|
||||
()
|
||||
)
|
||||
}
|
||||
|
||||
let ff2b = (
|
||||
~name: string,
|
||||
~fn: (float, float) => bool,
|
||||
~nameSpace="",
|
||||
~requiresNamespace=false,
|
||||
~examples=?,
|
||||
(),
|
||||
) => {
|
||||
Function.make(
|
||||
~name,
|
||||
~nameSpace,
|
||||
~requiresNamespace=requiresNamespace,
|
||||
~examples=examples->E.O.default([], _),
|
||||
~output=EvtBool,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=((inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvBool->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}),
|
||||
()
|
||||
)
|
||||
],
|
||||
()
|
||||
)
|
||||
}
|
||||
|
||||
let bb2b = (
|
||||
~name: string,
|
||||
~fn: (bool, bool) => bool,
|
||||
~nameSpace="",
|
||||
~requiresNamespace=false,
|
||||
~examples=?,
|
||||
(),
|
||||
) => {
|
||||
Function.make(
|
||||
~name,
|
||||
~nameSpace,
|
||||
~requiresNamespace=requiresNamespace,
|
||||
~examples=examples->E.O.default([], _),
|
||||
~output=EvtBool,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeBool, FRTypeBool],
|
||||
~run=((inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvBool(x), IEvBool(y)] => fn(x, y)->IEvBool->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}),
|
||||
()
|
||||
)
|
||||
],
|
||||
()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ let fnList = Belt.Array.concatMany([
|
|||
FR_GenericDist.library,
|
||||
FR_Units.library,
|
||||
FR_Date.library,
|
||||
FR_Mathjs.library,
|
||||
FR_Math.library,
|
||||
])
|
||||
|
||||
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
||||
|
|
|
@ -9,35 +9,33 @@ The main interface is fairly constrained. Basically, write functions like the fo
|
|||
~name="Normal",
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="Normal",
|
||||
~definitions=[
|
||||
FnDefinition.make(~name="normal", ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=(
|
||||
inputs,
|
||||
env,
|
||||
) =>
|
||||
inputs
|
||||
->Prepare.ToValueTuple.twoDistOrNumber
|
||||
->E.R.bind(
|
||||
Process.twoDistsOrNumbersToDistUsingSymbolicDist(
|
||||
~fn=E.Tuple2.toFnCall(SymbolicDist.Normal.make),
|
||||
~env,
|
||||
~values=_,
|
||||
),
|
||||
)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
~name="normal",
|
||||
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
|
||||
~run=(
|
||||
inputs,
|
||||
env,
|
||||
) =>
|
||||
inputs
|
||||
->Prepare.ToValueTuple.twoDistOrNumber
|
||||
->E.R.bind(
|
||||
Process.twoDistsOrNumbersToDistUsingSymbolicDist(
|
||||
~fn=E.Tuple2.toFnCall(SymbolicDist.Normal.make),
|
||||
~env,
|
||||
~values=_,
|
||||
),
|
||||
],
|
||||
)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
The Function name is just there for future documentation. The function defintions
|
||||
The Function name is just there for future documentation.
|
||||
|
||||
## Key Files
|
||||
|
||||
**FunctionRegistry_Core**
|
||||
Key types, internal functionality, and a `Registry` module with a `matchAndRun` function to call function definitions.
|
||||
Key types, internal functionality, and a `Registry` module with a `call` function to call function definitions.
|
||||
|
||||
**FunctionRegistry_Library**
|
||||
A list of all the Functions defined in the Function Registry.
|
||||
|
@ -45,4 +43,4 @@ A list of all the Functions defined in the Function Registry.
|
|||
The definition arrays are stored in `FR_*` modules, by convention.
|
||||
|
||||
**FunctionRegistry_Helpers**
|
||||
A list of helper functions for the FunctionRegistry_Library.
|
||||
A list of helper functions for the `FunctionRegistry_Library`.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
module Eval = Reducer_MathJs_Eval
|
|
@ -1,27 +0,0 @@
|
|||
module JavaScript = Reducer_Js
|
||||
open ReducerInterface_InternalExpressionValue
|
||||
open Reducer_ErrorValue
|
||||
|
||||
@module("mathjs") external dummy_: string => unit = "evaluate"
|
||||
let dummy1_ = dummy_ //Deceive the compiler to make the import although we wont make a call from rescript. Otherwise the optimizer deletes the import
|
||||
|
||||
type answer = {"value": unit}
|
||||
|
||||
/*
|
||||
The result has to be delivered in an object so that we can type cast.
|
||||
Rescript cannot type cast on basic values passed on their own.
|
||||
This is why we call evalua inside Javascript and wrap the result in an Object
|
||||
*/
|
||||
let eval__: string => 'a = %raw(`function (expr) { return {value: Mathjs.evaluate(expr)}; }`)
|
||||
|
||||
/*
|
||||
Call MathJs evaluate and return as a variant
|
||||
*/
|
||||
let eval = (expr: string): result<internalExpressionValue, errorValue> => {
|
||||
try {
|
||||
let answer = eval__(expr)
|
||||
answer["value"]->JavaScript.Gate.jsToIEv
|
||||
} catch {
|
||||
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||
}
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
module ExternalLibrary = ReducerInterface_ExternalLibrary
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module StdLib = ReducerInterface_StdLib
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
|
||||
/*
|
||||
Map external calls of Reducer
|
||||
*/
|
||||
let dispatch = (
|
||||
call: InternalExpressionValue.functionCall,
|
||||
environment: Reducer_T.environment,
|
||||
reducer: Reducer_T.reducerFn,
|
||||
chain,
|
||||
): result<Reducer_T.value, 'e> => {
|
||||
E.A.O.firstSomeFn([// () => ReducerInterface_GenericDistribution.dispatch(call, environment),
|
||||
// () => ReducerInterface_Date.dispatch(call, environment),
|
||||
// () => ReducerInterface_Duration.dispatch(call, environment),
|
||||
// () => ReducerInterface_Number.dispatch(call, environment),
|
||||
// () => FunctionRegistry_Library.dispatch(call, environment, reducer),
|
||||
])->E.O2.defaultFn(() => chain(call, environment, reducer))
|
||||
}
|
||||
|
||||
/*
|
||||
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||
|
||||
The final chain(call) invokes the builtin default functions of the interpreter.
|
||||
|
||||
Via chain(call), all MathJs operators and functions are available for string, number, boolean, array and record
|
||||
.e.g + - / * > >= < <= == /= not and or sin cos log ln concat, etc.
|
||||
|
||||
// See https://mathjs.org/docs/expressions/syntax.html
|
||||
// See https://mathjs.org/docs/reference/functions.html
|
||||
|
||||
Remember from the users point of view, there are no different modules:
|
||||
// "doSth( constructorType1 )"
|
||||
// "doSth( constructorType2 )"
|
||||
doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher.
|
||||
*/
|
|
@ -1,11 +1,13 @@
|
|||
exception ErrorException = Reducer_ErrorValue.ErrorException
|
||||
|
||||
let internalStdLib: Reducer_T.namespace = {
|
||||
// constants
|
||||
let res =
|
||||
Reducer_Namespace.make()
|
||||
->Reducer_Namespace.mergeFrom(SquiggleLibrary_Math.make())
|
||||
->Reducer_Namespace.mergeFrom(SquiggleLibrary_Versions.make())
|
||||
|
||||
// array and record lookups
|
||||
let res = res->Reducer_Namespace.set(
|
||||
"$_atIndex_$",
|
||||
Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => {
|
||||
|
@ -32,6 +34,7 @@ let internalStdLib: Reducer_T.namespace = {
|
|||
})->Reducer_T.IEvLambda,
|
||||
)
|
||||
|
||||
// some lambdas can't be expressed in function registry (e.g. `mx` with its variadic number of parameters)
|
||||
let res = FunctionRegistry_Library.nonRegistryLambdas->Belt.Array.reduce(res, (
|
||||
cur,
|
||||
(name, lambda),
|
||||
|
@ -39,15 +42,7 @@ let internalStdLib: Reducer_T.namespace = {
|
|||
cur->Reducer_Namespace.set(name, lambda->Reducer_T.IEvLambda)
|
||||
})
|
||||
|
||||
// Reducer_Dispatch_BuiltIn:
|
||||
|
||||
// [ ] | (_, [IEvBool(_)])
|
||||
// [ ] | (_, [IEvNumber(_)])
|
||||
// [ ] | (_, [IEvString(_)])
|
||||
// [ ] | (_, [IEvBool(_), IEvBool(_)])
|
||||
// [ ] | (_, [IEvNumber(_), IEvNumber(_)])
|
||||
// [ ] | (_, [IEvString(_), IEvString(_)]) => callMathJs(call)
|
||||
|
||||
// bind the entire FunctionRegistry
|
||||
let res =
|
||||
FunctionRegistry_Library.registry
|
||||
->FunctionRegistry_Core.Registry.allNames
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export type Dict_t<T> = { [key: string]: T };
|
Loading…
Reference in New Issue
Block a user