tests; drop mathjs; new FR helpers; disable type tests

This commit is contained in:
Vyacheslav Matyukhin 2022-09-19 21:46:37 +04:00
parent 271303fb5f
commit f8b743feb5
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
22 changed files with 342 additions and 529 deletions

View File

@ -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)")
// })

View File

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

View File

@ -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")),
)
)
})

View File

@ -3,22 +3,22 @@ open Reducer_Peggy_TestHelpers
describe("Peggy void", () => { describe("Peggy void", () => {
//literal //literal
testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ()) testToExpression("()", "()", ~v="()", ())
testToExpression( testToExpression(
"fn()=1", "fn()=1",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}", "fn = {|_| {1}}",
// ~v="@{fn: lambda(_=>internal code)}", // ~v="@{fn: lambda(_=>internal code)}",
(), (),
) )
testToExpression( testToExpression(
"fn()=1; fn()", "fn()=1; fn()",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () (:fn ()))}", "fn = {|_| {1}}; (fn)(())",
~v="1", ~v="1",
(), (),
) )
testToExpression( testToExpression(
"fn(a)=(); call fn(1)", "fn(a)=(); call fn(1)",
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}", "fn = {|a| {()}}; _ = {(fn)(1)}",
// ~v="@{_: (),fn: lambda(a=>internal code)}", // ~v="@{_: (),fn: lambda(a=>internal code)}",
(), (),
) )

View File

@ -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)
// }

View File

@ -10,3 +10,7 @@ describe("Parse function assignment", () => {
describe("Evaluate function assignment", () => { describe("Evaluate function assignment", () => {
testEvalToBe("f(x)=x; f(1)", "Ok(1)") testEvalToBe("f(x)=x; f(1)", "Ok(1)")
}) })
describe("Shadowing", () => {
testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)")
})

View File

@ -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, addone)", "Error???")
testEvalToBe("addone(x)=x+1; map(2, {x: 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])")
})

View File

@ -2,20 +2,29 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("eval", () => { 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", () => { describe("expressions", () => {
testEvalToBe("1", "Ok(1)") testEvalToBe("1", "Ok(1)")
testEvalToBe("-1", "Ok(-1)")
testEvalToBe("1-1", "Ok(0)")
testEvalToBe("1+2", "Ok(3)") testEvalToBe("1+2", "Ok(3)")
testEvalToBe("(1+2)*3", "Ok(9)") testEvalToBe("(1+2)*3", "Ok(9)")
testEvalToBe("2>1", "Ok(true)") testEvalToBe("2>1", "Ok(true)")
testEvalToBe("concat('a ', 'b')", "Ok('a b')") testEvalToBe("concat('a ', 'b')", "Ok('a b')")
testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])") testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])")
testEvalToBe("log(10)", "Ok(2.302585092994046)") testEvalToBe("log(10)", "Ok(2.302585092994046)")
testEvalToBe("cos(10)", "Ok(-0.8390715290764524)") testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)")
// TODO more built ins // 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", () => { describe("arrays", () => {
test("empty array", () => expectEvalToBe("[]", "Ok([])")) test("empty array", () => expectEvalToBe("[]", "Ok([])"))
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])") testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
@ -51,6 +60,10 @@ describe("eval", () => {
testEvalError("1; 1") testEvalError("1; 1")
testEvalToBe("x=1; x=1; x", "Ok(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", () => { describe("test exceptions", () => {

View File

@ -10,5 +10,7 @@ module.exports = {
"/node_modules/", "/node_modules/",
".*Helpers.bs.js", ".*Helpers.bs.js",
".*Helpers.ts", ".*Helpers.ts",
".*Reducer_Type.*",
".*_type_test.bs.js",
], ],
}; };

View File

@ -25,52 +25,87 @@ let makeFn = (
fn: array<internalExpressionValue> => result<internalExpressionValue, errorValue>, fn: array<internalExpressionValue> => result<internalExpressionValue, errorValue>,
) => makeFnMany(name, [{inputs: inputs, fn: fn}]) ) => 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 = [ let library = [
makeFF2F("add", (x, y) => x +. y), // infix + (see Reducer/Reducer_Peggy/helpers.ts) Make.ff2f(
makeFF2F("subtract", (x, y) => x -. y), // infix - ~name="add", // infix + (see Reducer/Reducer_Peggy/helpers.ts)
makeFF2F("multiply", (x, y) => x *. y), // infix * ~fn=(x, y) => x +. y,
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 == Make.ff2f(
makeFF2B("smaller", (x, y) => x < y), // infix < ~name="subtract", // infix -
makeFF2B("smallerEq", (x, y) => x <= y), // infix <= ~fn=(x, y) => x -. y,
makeFF2B("larger", (x, y) => x > y), // infix > ()
makeFF2B("largerEq", (x, y) => x >= y), // infix >= ),
makeBB2B("or", (x, y) => x || y), // infix || Make.ff2f(
makeBB2B("and", (x, y) => x && y), // infix && ~name="multiply", // infix *
makeFn("unaryMinus", [FRTypeNumber], inputs => { // unary prefix - ~fn=(x, y) => x *. y,
switch inputs { ()
| [IEvNumber(x)] => IEvNumber(-.x)->Ok ),
| _ => Error(impossibleError) 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 ! makeFn("not", [FRTypeNumber], inputs => { // unary prefix !
switch inputs { switch inputs {
| [IEvNumber(x)] => IEvBool(x != 0.)->Ok | [IEvNumber(x)] => IEvBool(x != 0.)->Ok
@ -120,4 +155,12 @@ let library = [
| _ => Error(impossibleError) | _ => Error(impossibleError)
} }
}), }),
makeFn("javascriptraise", [FRTypeAny], inputs => {
switch inputs {
| [msg] => {
Js.Exn.raiseError(msg->ReducerInterface_InternalExpressionValue.toString)
}
| _ => Error(impossibleError)
}
}),
] ]

View 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,
(),
),
]

View File

@ -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"),
]

View File

@ -20,13 +20,12 @@ module ArrayNumberDist = {
} }
let library = [ let library = [
Function.make( Make.f2f(
~name="floor", ~name="floor",
~nameSpace, ~nameSpace,
~requiresNamespace, ~requiresNamespace,
~output=EvtNumber,
~examples=[`floor(3.5)`], ~examples=[`floor(3.5)`],
~definitions=[DefineFn.Numbers.oneToOne("floor", Js.Math.floor_float)], ~fn=Js.Math.floor_float,
(), (),
), ),
Function.make( Function.make(

View File

@ -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)
}),
()
)
],
()
)
}
}

View File

@ -12,7 +12,7 @@ let fnList = Belt.Array.concatMany([
FR_GenericDist.library, FR_GenericDist.library,
FR_Units.library, FR_Units.library,
FR_Date.library, FR_Date.library,
FR_Mathjs.library, FR_Math.library,
]) ])
let registry = FunctionRegistry_Core.Registry.make(fnList) let registry = FunctionRegistry_Core.Registry.make(fnList)

View File

@ -9,35 +9,33 @@ The main interface is fairly constrained. Basically, write functions like the fo
~name="Normal", ~name="Normal",
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="Normal", ~name="normal",
~definitions=[ ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
FnDefinition.make(~name="normal", ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=( ~run=(
inputs, inputs,
env, env,
) => ) =>
inputs inputs
->Prepare.ToValueTuple.twoDistOrNumber ->Prepare.ToValueTuple.twoDistOrNumber
->E.R.bind( ->E.R.bind(
Process.twoDistsOrNumbersToDistUsingSymbolicDist( Process.twoDistsOrNumbersToDistUsingSymbolicDist(
~fn=E.Tuple2.toFnCall(SymbolicDist.Normal.make), ~fn=E.Tuple2.toFnCall(SymbolicDist.Normal.make),
~env, ~env,
~values=_, ~values=_,
),
)
->E.R2.fmap(Wrappers.evDistribution)
), ),
], )
->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 ## Key Files
**FunctionRegistry_Core** **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** **FunctionRegistry_Library**
A list of all the Functions defined in the Function Registry. 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. The definition arrays are stored in `FR_*` modules, by convention.
**FunctionRegistry_Helpers** **FunctionRegistry_Helpers**
A list of helper functions for the FunctionRegistry_Library. A list of helper functions for the `FunctionRegistry_Library`.

View File

@ -1 +0,0 @@
module Eval = Reducer_MathJs_Eval

View File

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

View File

@ -1,3 +1,2 @@
module ExternalLibrary = ReducerInterface_ExternalLibrary
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module StdLib = ReducerInterface_StdLib module StdLib = ReducerInterface_StdLib

View File

@ -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.
*/

View File

@ -1,11 +1,13 @@
exception ErrorException = Reducer_ErrorValue.ErrorException exception ErrorException = Reducer_ErrorValue.ErrorException
let internalStdLib: Reducer_T.namespace = { let internalStdLib: Reducer_T.namespace = {
// constants
let res = let res =
Reducer_Namespace.make() Reducer_Namespace.make()
->Reducer_Namespace.mergeFrom(SquiggleLibrary_Math.make()) ->Reducer_Namespace.mergeFrom(SquiggleLibrary_Math.make())
->Reducer_Namespace.mergeFrom(SquiggleLibrary_Versions.make()) ->Reducer_Namespace.mergeFrom(SquiggleLibrary_Versions.make())
// array and record lookups
let res = res->Reducer_Namespace.set( let res = res->Reducer_Namespace.set(
"$_atIndex_$", "$_atIndex_$",
Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => { Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => {
@ -32,6 +34,7 @@ let internalStdLib: Reducer_T.namespace = {
})->Reducer_T.IEvLambda, })->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, ( let res = FunctionRegistry_Library.nonRegistryLambdas->Belt.Array.reduce(res, (
cur, cur,
(name, lambda), (name, lambda),
@ -39,15 +42,7 @@ let internalStdLib: Reducer_T.namespace = {
cur->Reducer_Namespace.set(name, lambda->Reducer_T.IEvLambda) cur->Reducer_Namespace.set(name, lambda->Reducer_T.IEvLambda)
}) })
// Reducer_Dispatch_BuiltIn: // bind the entire FunctionRegistry
// [ ] | (_, [IEvBool(_)])
// [ ] | (_, [IEvNumber(_)])
// [ ] | (_, [IEvString(_)])
// [ ] | (_, [IEvBool(_), IEvBool(_)])
// [ ] | (_, [IEvNumber(_), IEvNumber(_)])
// [ ] | (_, [IEvString(_), IEvString(_)]) => callMathJs(call)
let res = let res =
FunctionRegistry_Library.registry FunctionRegistry_Library.registry
->FunctionRegistry_Core.Registry.allNames ->FunctionRegistry_Core.Registry.allNames

View File

@ -1 +0,0 @@
export type Dict_t<T> = { [key: string]: T };