diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res new file mode 100644 index 00000000..1cc238a9 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res @@ -0,0 +1,94 @@ +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module ExpressionT = Reducer_Expression_T +module Module = Reducer_Module +module Bindings = Reducer_Module +module ErrorValue = Reducer_ErrorValue + +open Jest +open Expect + +// ---------------------- +// --- Start of Module File +// ---------------------- + +module FooImplementation = { + let fooNumber = 0.0 + let fooString = "Foo String" + let fooBool = true + let makeFoo = (a: string, b: string, _environment): string => `I am ${a}-foo and I am ${b}-foo` + let makeBar = (a: float, b: float, _environment): string => + `I am ${a->Js.Float.toString}-bar and I am ${b->Js.Float.toString}-bar` +} + +module FooFFI = { + let makeFoo: ExpressionT.optionFfiFn = (args: array, environment) => { + switch args { + | [IEvString(a), IEvString(b)] => FooImplementation.makeFoo(a, b, environment)->IEvString->Some + | _ => None + } + } + let makeBar: ExpressionT.optionFfiFn = (args: array, environment) => + switch args { + | [IEvNumber(a), IEvNumber(b)] => FooImplementation.makeBar(a, b, environment)->IEvString->Some + | _ => None + } +} + +let fooModule: Module.t = + Module.emptyStdLib + ->Module.defineNumber("fooNumber", FooImplementation.fooNumber) + ->Module.defineString("fooString", FooImplementation.fooString) + ->Module.defineBool("fooBool", FooImplementation.fooBool) + ->Module.defineFunction("makeFoo", FooFFI.makeFoo) + ->Module.defineFunction("makeBar", FooFFI.makeBar) + +let makeBindings = (prevBindings: Bindings.t): Bindings.t => + prevBindings->Module.defineModule("Foo", fooModule) + +// ---------------------- +// --- End of Module File +// ---------------------- + +let stdLibWithFoo = Bindings.emptyBindings->makeBindings +let evalWithFoo = sourceCode => + Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr => + Reducer_Expression.reduceExpression( + expr, + stdLibWithFoo, + InternalExpressionValue.defaultEnvironment, + ) + ) +let evalToStringResultWithFoo = sourceCode => + evalWithFoo(sourceCode)->InternalExpressionValue.toStringResult + +describe("Module", () => { + test("fooNumber", () => { + let result = evalToStringResultWithFoo("Foo.fooNumber") + expect(result)->toEqual("Ok(0)") + }) + test("fooString", () => { + let result = evalToStringResultWithFoo("Foo.fooString") + expect(result)->toEqual("Ok('Foo String')") + }) + test("fooBool", () => { + let result = evalToStringResultWithFoo("Foo.fooBool") + expect(result)->toEqual("Ok(true)") + }) + test("fooBool", () => { + let result = evalToStringResultWithFoo("Foo.fooBool") + expect(result)->toEqual("Ok(true)") + }) + test("makeFoo", () => { + let result = evalToStringResultWithFoo("Foo.makeFoo('a', 'b')") + expect(result)->toEqual("Ok('I am a-foo and I am b-foo')") + }) + test("makeFoo wrong arguments", () => { + let result = evalToStringResultWithFoo("Foo.makeFoo(1, 2)") + // Notice the error with types + expect(result)->toEqual("Error(Function not found: makeFoo(Number,Number))") + }) + test("makeBar", () => { + let result = evalToStringResultWithFoo("Foo.makeBar(1, 2)") + expect(result)->toEqual("Ok('I am 1-bar and I am 2-bar')") + }) +}) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res index 717b4b1d..bf52b398 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res @@ -19,12 +19,19 @@ let checkArity = ( lambdaValue: ExpressionValue.lambdaValue, args: list, ) => { - let argsLength = Belt.List.length(args) - let parametersLength = Js.Array2.length(lambdaValue.parameters) - if argsLength !== parametersLength { - ErrorValue.REArityError(None, parametersLength, argsLength)->Error - } else { - args->Ok + let reallyCheck = { + let argsLength = Belt.List.length(args) + let parametersLength = Js.Array2.length(lambdaValue.parameters) + if argsLength !== parametersLength { + ErrorValue.REArityError(None, parametersLength, argsLength)->Error + } else { + args->Ok + } + } + let exprOrFFI = castInternalCodeToExpression(lambdaValue.body) + switch exprOrFFI { + | NotFFI(_) => reallyCheck + | FFI(_) => args->Ok } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index 38910da2..dee70c88 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -71,6 +71,8 @@ type ffiFn = ( environment, ) => result +type optionFfiFn = (array, environment) => option + type expressionOrFFI = | NotFFI(expression) | FFI(ffiFn) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res index 9f016f66..62f9d8c7 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res @@ -1,4 +1,6 @@ module ExpressionT = Reducer_Expression_T +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +open Reducer_ErrorValue open ReducerInterface_InternalExpressionValue let expressionValueToString = toString @@ -68,6 +70,7 @@ let set = (nameSpace: t, id: string, value): t => { } let emptyModule: t = NameSpace(emptyMap) +let emptyBindings = emptyModule let fromTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceFromTypeScriptBindings let toTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceToTypeScriptBindings @@ -110,16 +113,50 @@ let eLambdaFFIValue = (ffiFn: ExpressionT.ffiFn) => { }) } +let functionNotFoundError = (call: functionCall) => + REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)->Error + +let functionNotFoundErrorFFIFn = (functionName: string): ExpressionT.ffiFn => { + (args: array, _environment: environment): result< + internalExpressionValue, + errorValue, + > => { + let call = (functionName, args) + functionNotFoundError(call) + } +} + +let convertOptionToFfiFn = ( + myFunctionName: string, + myFunction: ExpressionT.optionFfiFn, +): ExpressionT.ffiFn => { + (args: array, environment) => { + myFunction(args, environment) + ->Belt.Option.map(v => v->Ok) + ->Belt.Option.getWithDefault(functionNotFoundErrorFFIFn(myFunctionName)(args, environment)) + } +} + // -- Module definition let define = (nameSpace: t, identifier: string, ev: internalExpressionValue): t => { let NameSpace(container) = nameSpace Belt.Map.String.set(container, identifier, ev)->NameSpace } + let defineNumber = (nameSpace: t, identifier: string, value: float): t => nameSpace->define(identifier, IEvNumber(value)) +let defineString = (nameSpace: t, identifier: string, value: string): t => + nameSpace->define(identifier, IEvString(value)) + +let defineBool = (nameSpace: t, identifier: string, value: bool): t => + nameSpace->define(identifier, IEvBool(value)) + let defineModule = (nameSpace: t, identifier: string, value: t): t => nameSpace->define(identifier, toExpressionValue(value)) -let defineFFI = (nameSpace: t, identifier: string, value: ExpressionT.ffiFn): t => - nameSpace->define(identifier, value->eLambdaFFIValue) +let defineFunction = (nameSpace: t, identifier: string, value: ExpressionT.optionFfiFn): t => { + nameSpace->define(identifier, convertOptionToFfiFn(identifier, value)->eLambdaFFIValue) +} + +let emptyStdLib: t = emptyModule->defineBool("stdlib", true) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module_Lambda.res index d14f5729..7c3c5e54 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module_Lambda.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module_Lambda.res @@ -1,25 +1,11 @@ module ExpressionBuilder = Reducer_Expression_ExpressionBuilder module ExpressionT = Reducer_Expression_T -open Reducer_ErrorValue -open ReducerInterface_InternalExpressionValue +// open ReducerInterface_InternalExpressionValue type expression = ExpressionT.expression -let defaultCase = (call: functionCall) => - REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)->Error - -let defaultCaseFFIFn = (functionName: string): ExpressionT.ffiFn => { - (args: array, _environment: environment): result< - internalExpressionValue, - errorValue, - > => { - let call = (functionName, args) - defaultCase(call) - } -} - let defaultCaseFFI = (functionName: string): expression => { - ExpressionBuilder.eLambdaFFI(defaultCaseFFIFn(functionName)) + ExpressionBuilder.eLambdaFFI(Reducer_Module.functionNotFoundErrorFFIFn(functionName)) } let addGuard = (