diff --git a/src/main/java/com/dashjoin/jsonata/Jsonata.java b/src/main/java/com/dashjoin/jsonata/Jsonata.java index 72266d5..d001020 100644 --- a/src/main/java/com/dashjoin/jsonata/Jsonata.java +++ b/src/main/java/com/dashjoin/jsonata/Jsonata.java @@ -1927,8 +1927,8 @@ Object evaluateLambda(Symbol expr, Object input, Frame environment) { result = partialApplyProcedure((Symbol)proc, (List)evaluatedArgs); } else if (Utils.isFunction(proc)) { result = partialApplyNativeFunction((JFunction)proc /*.implementation*/, evaluatedArgs); - // } else if (typeof proc === "function") { - // result = partialApplyNativeFunction(proc, evaluatedArgs); + } else if (proc instanceof JLambda) { + result = partialApplyNativeFunction(new JFunction((JLambda)proc, null), evaluatedArgs); } else { throw new JException("T1008", //stack: (new Error()).stack, @@ -2024,6 +2024,12 @@ Object partialApplyNativeFunction(JFunction _native, List args) { // create a lambda Object that wraps and invokes the native function // get the list of declared arguments from the native function // this has to be picked out from the toString() value + if (_native.getNumberOfArgs() < 0) { + // Java: parameters unknown (callable without signature), use the number of args supplied + var fn = new JFunction(_native.function, null); + fn.numberOfArgs = args.size(); + _native = fn; + } var sigArgs = getNativeFunctionArguments(_native); sigArgs = sigArgs.stream().map(sigArg -> { return "$" + sigArg; @@ -2087,34 +2093,34 @@ public static JFunction function(String name, String signature, Class clazz, Obj } public static JFunction function(String name, FnVarArgs func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn0 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn1 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn2 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn3 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn4 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn5 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn6 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn7 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } public static JFunction function(String name, Fn8 func, String signature) { - return new JFunction(func.getJFunctionCallable(), signature); + return new JFunction(func, signature); } /** @@ -2170,6 +2176,14 @@ public Frame createFrame(Frame enclosingEnvironment) { } public static interface JLambda { + JFunctionCallable getJFunctionCallable(); + + /** + * @return the number of declared parameters, -1 if unknown (varargs) + */ + default int getNumberOfArgs() { + return -1; + } } public static interface FnVarArgs extends JLambda, Function, R> { @@ -2180,21 +2194,25 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn0 extends JLambda, Supplier { + default int getNumberOfArgs() { return 0; } default JFunctionCallable getJFunctionCallable() { return (input, args) -> get(); } } public static interface Fn1 extends JLambda, Function { + default int getNumberOfArgs() { return 1; } default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0)); } } public static interface Fn2 extends JLambda, BiFunction { + default int getNumberOfArgs() { return 2; } default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1)); } } public static interface Fn3 extends JLambda { + default int getNumberOfArgs() { return 3; } R apply(A a, B b, C c); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2202,6 +2220,7 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn4 extends JLambda { + default int getNumberOfArgs() { return 4; } R apply(A a, B b, C c, D d); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2209,6 +2228,7 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn5 extends JLambda { + default int getNumberOfArgs() { return 5; } R apply(A a, B b, C c, D d, E e); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2216,6 +2236,7 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn6 extends JLambda { + default int getNumberOfArgs() { return 6; } R apply(A a, B b, C c, D d, E e, F f); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2224,6 +2245,7 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn7 extends JLambda { + default int getNumberOfArgs() { return 7; } R apply(A a, B b, C c, D d, E e, F f, G g); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2232,6 +2254,7 @@ default JFunctionCallable getJFunctionCallable() { } } public static interface Fn8 extends JLambda { + default int getNumberOfArgs() { return 8; } R apply(A a, B b, C c, D d, E e, F f, G g, H h); default JFunctionCallable getJFunctionCallable() { return (input, args) -> apply((A) args.get(0), (B) args.get(1), @@ -2260,6 +2283,12 @@ public static class JFunction implements JFunctionCallable, JFunctionSignatureVa Signature signature; Method method; Object methodInstance; + int numberOfArgs = -1; + + public JFunction(JLambda lambda, String signature) { + this(lambda.getJFunctionCallable(), signature); + this.numberOfArgs = lambda.getNumberOfArgs(); + } public JFunction(JFunctionCallable function, String signature) { this.function = function; @@ -2306,11 +2335,16 @@ public Object validate(Object args, Object context) { return args; } + /** + * @return the number of declared parameters, -1 if unknown + */ public int getNumberOfArgs() { if (method != null) return method.getParameterTypes().length; + if (numberOfArgs >= 0) + return numberOfArgs; // Java: a JFunctionCallable has no declared parameters, use the signature (if any) - return signature != null ? signature.getNumberOfArgs() : 0; + return signature != null ? signature.getNumberOfArgs() : -1; } } diff --git a/src/test/java/com/dashjoin/jsonata/OldPrsTest.java b/src/test/java/com/dashjoin/jsonata/OldPrsTest.java index 7d820f1..d77d9d6 100644 --- a/src/test/java/com/dashjoin/jsonata/OldPrsTest.java +++ b/src/test/java/com/dashjoin/jsonata/OldPrsTest.java @@ -9,7 +9,7 @@ import com.dashjoin.jsonata.Jsonata.JFunction; import com.dashjoin.jsonata.json.Json; -/** tests from PRs #80, #85, #87, adapted to the existing API (partial application of custom functions without signature: see #83) */ +/** tests from PRs #80, #85, #87, adapted to the existing API */ public class OldPrsTest { static Object res(String name) throws Exception { @@ -50,6 +50,28 @@ public void testFunctionWithInputData() { Assertions.assertEquals(9, expression.evaluate(Map.of("factor", "3"))); } + @SuppressWarnings("unchecked") + @Test + public void testFunctionWithInputData2() { + JFunction multiply = new JFunction((input, args) -> { + Object first = args.get(0); + Object second = args.size() > 1 ? args.get(1) + : Integer.parseInt(((Map) input).get("factor").toString()); + return (Integer) first * (Integer) second; + }, null); + var expression = Jsonata.jsonata("$multiply(3) ~> $multiply(?, 2)"); + expression.registerFunction("multiply", multiply); + Assertions.assertEquals(18, expression.evaluate(Map.of("factor", "3"))); + } + + @Test + public void testCustomFunctionPipe() { + JFunction add = new JFunction((input, args) -> (Integer) args.get(0) + (Integer) args.get(1), null); + var expression = Jsonata.jsonata("$add(1, 2) ~> $add(?, 3)"); + expression.registerFunction("add", add); + Assertions.assertEquals(6, expression.evaluate(null)); + } + // #87 @Test public void testMapWithAppend() { diff --git a/src/test/java/com/dashjoin/jsonata/PartialTest.java b/src/test/java/com/dashjoin/jsonata/PartialTest.java index afc894e..64297ad 100644 --- a/src/test/java/com/dashjoin/jsonata/PartialTest.java +++ b/src/test/java/com/dashjoin/jsonata/PartialTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.dashjoin.jsonata.Jsonata.JFunction; +import com.dashjoin.jsonata.Jsonata.Fn2; import com.dashjoin.jsonata.Jsonata.JFunctionCallable; /** @@ -72,4 +73,36 @@ public void missingDollar() { var ex = Assertions.assertThrows(JException.class, () -> jsonata("substring(?, 1)").evaluate(null)); Assertions.assertEquals("T1007", ex.getError()); } + + // #83: a JFunctionCallable without signature: arity = number of args supplied + @Test + public void customFunctionWithoutSignature() { + var expression = jsonata("$add(1, 2) ~> $add(?, 3)"); + expression.registerFunction("add", new JFunction(new JFunctionCallable() { + @SuppressWarnings("rawtypes") + @Override + public Object call(Object input, List args) throws Throwable { + return (Integer) args.get(0) + (Integer) args.get(1); + } + }, null)); + Assertions.assertEquals(6, expression.evaluate(null)); + } + + // Fn* factories without signature: arity of the lambda + @Test + public void fnFactoryWithoutSignature() { + var expression = jsonata("$add(1, 2) ~> $add(?, 3)"); + expression.registerFunction("add", + Jsonata.function("add", (Fn2) (a, b) -> a + b, null)); + Assertions.assertEquals(6, expression.evaluate(null)); + } + + // Fn* lambdas bound via Frame.bind can be partially applied + @Test + public void boundLambda() { + var expression = jsonata("$f(1, 2) ~> $f(?, 3)"); + var frame = expression.createFrame(); + frame.bind("f", (Fn2) (a, b) -> a + b); + Assertions.assertEquals(6, expression.evaluate(null, frame)); + } }