Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 47 additions & 13 deletions src/main/java/com/dashjoin/jsonata/Jsonata.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2087,34 +2093,34 @@ public static JFunction function(String name, String signature, Class clazz, Obj
}

public static<A,B,R> JFunction function(String name, FnVarArgs<R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,R> JFunction function(String name, Fn0<R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,R> JFunction function(String name, Fn1<A,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,R> JFunction function(String name, Fn2<A,B,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,R> JFunction function(String name, Fn3<A,B,C,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,D,R> JFunction function(String name, Fn4<A,B,C,D,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,D,E,R> JFunction function(String name, Fn5<A,B,C,D,E,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,D,E,F,R> JFunction function(String name, Fn6<A,B,C,D,E,F,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,D,E,F,G,R> JFunction function(String name, Fn7<A,B,C,D,E,F,G,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}
public static<A,B,C,D,E,F,G,H,R> JFunction function(String name, Fn8<A,B,C,D,E,F,G,H,R> func, String signature) {
return new JFunction(func.getJFunctionCallable(), signature);
return new JFunction(func, signature);
}

/**
Expand Down Expand Up @@ -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<R> extends JLambda, Function<List<?>, R> {
Expand All @@ -2180,42 +2194,49 @@ default JFunctionCallable getJFunctionCallable() {
}
}
public static interface Fn0<R> extends JLambda, Supplier<R> {
default int getNumberOfArgs() { return 0; }
default JFunctionCallable getJFunctionCallable() {
return (input, args) -> get();
}
}
public static interface Fn1<A,R> extends JLambda, Function<A,R> {
default int getNumberOfArgs() { return 1; }
default JFunctionCallable getJFunctionCallable() {
return (input, args) -> apply((A) args.get(0));
}
}
public static interface Fn2<A,B,R> extends JLambda, BiFunction<A,B,R> {
default int getNumberOfArgs() { return 2; }
default JFunctionCallable getJFunctionCallable() {
return (input, args) -> apply((A) args.get(0), (B) args.get(1));
}
}
public static interface Fn3<A,B,C,R> 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),
(C) args.get(2));
}
}
public static interface Fn4<A,B,C,D,R> 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),
(C) args.get(2), (D) args.get(3));
}
}
public static interface Fn5<A,B,C,D,E,R> 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),
(C) args.get(2), (D) args.get(3), (E) args.get(4));
}
}
public static interface Fn6<A,B,C,D,E,F,R> 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),
Expand All @@ -2224,6 +2245,7 @@ default JFunctionCallable getJFunctionCallable() {
}
}
public static interface Fn7<A,B,C,D,E,F,G,R> 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),
Expand All @@ -2232,6 +2254,7 @@ default JFunctionCallable getJFunctionCallable() {
}
}
public static interface Fn8<A,B,C,D,E,F,G,H,R> 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),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down
24 changes: 23 additions & 1 deletion src/test/java/com/dashjoin/jsonata/OldPrsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String, Object>) 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() {
Expand Down
33 changes: 33 additions & 0 deletions src/test/java/com/dashjoin/jsonata/PartialTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<Integer, Integer, Integer>) (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<Integer, Integer, Integer>) (a, b) -> a + b);
Assertions.assertEquals(6, expression.evaluate(null, frame));
}
}
Loading