Repository navigation
Align partial function application with jsonata-js (#95) #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ab7e60b
Align partial function application with jsonata-js #95
aeberhart cb16ba0
Use Collectors.toList() for JDK 11 compatibility
aeberhart ef116f6
Partial application of callables and lambdas without declared arity #95
aeberhart dd89e91
Add test cases from #80, #85, #87
aeberhart 64ec3c7
Iterate arrays in $each / $merge without copying them (review)
aeberhart File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1742,16 +1742,16 @@ Jsonata getPerThreadInstance() { | |
| // trampoline loop - this gets invoked as a result of tail-call optimization | ||
| // the Object returned a tail-call thunk | ||
| // unpack it, evaluate its arguments, and apply the tail call | ||
| var next = /* await */ evaluate(((Symbol)result).body.procedure, ((Symbol)result).input, ((Symbol)result).environment); | ||
| if(((Symbol)result).body.procedure.type == "variable") { | ||
| var next = /* await */ evaluate(((Symbol)((Symbol)result).body).procedure, ((Symbol)result).input, ((Symbol)result).environment); | ||
| if(((Symbol)((Symbol)result).body).procedure.type == "variable") { | ||
| if (next instanceof Symbol) // Java: not if JFunction | ||
| ((Symbol)next).token = ((Symbol)result).body.procedure.value; | ||
| ((Symbol)next).token = ((Symbol)((Symbol)result).body).procedure.value; | ||
| } | ||
| if (next instanceof Symbol) // Java: not if JFunction | ||
| ((Symbol)next).position = ((Symbol)result).body.procedure.position; | ||
| ((Symbol)next).position = ((Symbol)((Symbol)result).body).procedure.position; | ||
| var evaluatedArgs = new ArrayList<>(); | ||
| for(var ii = 0; ii < ((Symbol)result).body.arguments.size(); ii++) { | ||
| evaluatedArgs.add(/* await */ evaluate(((Symbol)result).body.arguments.get(ii), ((Symbol)result).input, ((Symbol)result).environment)); | ||
| for(var ii = 0; ii < ((Symbol)((Symbol)result).body).arguments.size(); ii++) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe extract (Symbol)result to var? |
||
| evaluatedArgs.add(/* await */ evaluate(((Symbol)((Symbol)result).body).arguments.get(ii), ((Symbol)result).input, ((Symbol)result).environment)); | ||
| } | ||
|
|
||
| result = /* await */ applyInner(next, evaluatedArgs, input, environment); | ||
|
|
@@ -1908,15 +1908,15 @@ Object evaluateLambda(Symbol expr, Object input, Frame environment) { | |
| var evaluatedArgs = new ArrayList<>(); | ||
| for(var ii = 0; ii < expr.arguments.size(); ii++) { | ||
| var arg = expr.arguments.get(ii); | ||
| if (arg.type.equals("operator") && (arg.value.equals("?"))) { | ||
| if (arg.type.equals("operator") && (""+arg.value).equals("?")) { | ||
|
aeberhart marked this conversation as resolved.
Resolved
|
||
| evaluatedArgs.add(arg); | ||
| } else { | ||
| evaluatedArgs.add(/* await */ evaluate(arg, input, environment)); | ||
| } | ||
| } | ||
| // lookup the procedure | ||
| var proc = /* await */ evaluate(expr.procedure, input, environment); | ||
| if (proc != null && expr.procedure.type.equals("path") && environment.lookup((String)expr.procedure.steps.get(0).value)!=null) { | ||
| if (proc == null && expr.procedure.type.equals("path") && environment.lookup((String)expr.procedure.steps.get(0).value)!=null) { | ||
|
aeberhart marked this conversation as resolved.
Resolved
|
||
| // help the user out here if they simply forgot the leading $ | ||
| throw new JException("T1007", | ||
| expr.position, | ||
|
|
@@ -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, | ||
|
|
@@ -1973,12 +1973,12 @@ Object validateArguments(Object signature, Object args, Object context) { | |
| if (i>=args.size()) break; | ||
| env.bind(""+proc.arguments.get(i).value, args.get(i)); | ||
| } | ||
| if (proc.body instanceof Symbol) { | ||
| result = evaluate(proc.body, proc.input, env); | ||
| if (proc.body instanceof JFunction) { | ||
| // this is a lambda that wraps a native Object - generated by partially evaluating a native | ||
| result = /* await */ applyNativeFunction((JFunction)proc.body, env); | ||
| } else if (proc.body instanceof Symbol) { | ||
| result = evaluate((Symbol)proc.body, proc.input, env); | ||
| } else throw new Error("Cannot execute procedure: "+proc+" "+proc.body); | ||
| // if (typeof proc.body === "function") { | ||
| // // this is a lambda that wraps a native Object - generated by partially evaluating a native | ||
| // result = /* await */ applyNativeFunction(proc.body, env); | ||
| return result; | ||
| } | ||
|
|
||
|
|
@@ -1997,7 +1997,7 @@ Object partialApplyProcedure(Symbol proc, List<Symbol> args) { | |
| for (var param : proc.arguments) { | ||
| // proc.arguments.forEach(Object (param, index) { | ||
| Object arg = index<args.size() ? args.get(index) : null; | ||
| if ((arg==null) || (arg instanceof Symbol && ("operator".equals(((Symbol)arg).type) && "?".equals(((Symbol)arg).value)))) { | ||
| if (arg instanceof Symbol && ("operator".equals(((Symbol)arg).type) && "?".equals(""+((Symbol)arg).value))) { | ||
|
aeberhart marked this conversation as resolved.
Resolved
|
||
| unboundArgs.add(param); | ||
| } else { | ||
| env.bind((String)param.value, arg); | ||
|
|
@@ -2024,58 +2024,53 @@ 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 | ||
|
|
||
|
|
||
| //var body = "function($a,$c) { $substring($a,0,$c) }"; | ||
|
|
||
| List sigArgs = new ArrayList<>(); | ||
| List partArgs = new ArrayList<>(); | ||
| for (int i=0; i<_native.getNumberOfArgs(); i++) { | ||
| String argName = "$" + (char)('a'+i); | ||
| sigArgs.add(argName); | ||
| if (i>=args.size() || args.get(i)==null) | ||
| partArgs.add(argName); | ||
| else | ||
| partArgs.add(args.get(i)); | ||
| } | ||
|
|
||
| var body = "function(" + String.join(", ", sigArgs) + "){"; | ||
| body += "$"+_native.functionName+"("+String.join(", ", sigArgs) + ") }"; | ||
|
|
||
| if (log.isLoggable(Level.FINE)) log.fine("partial trampoline = "+body); | ||
|
|
||
| // var sigArgs = getNativeFunctionArguments(_native); | ||
| // sigArgs = sigArgs.stream().map(sigArg -> { | ||
| // return "$" + sigArg; | ||
| // }).toList(); | ||
| // var body = "function(" + String.join(", ", sigArgs) + "){ _ }"; | ||
| 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; | ||
| }).collect(Collectors.toList()); | ||
| var body = "function(" + String.join(", ", sigArgs) + "){ _ }"; | ||
|
|
||
| var bodyAST = parser.parse(body); | ||
| //bodyAST.body = _native; | ||
| bodyAST.body = _native; | ||
|
|
||
| var partial = partialApplyProcedure(bodyAST, (List)args); | ||
| return partial; | ||
| } | ||
|
|
||
| /** | ||
| * Apply native function | ||
| * @param {Object} proc - Procedure | ||
| * @param {Object} env - Environment | ||
| * @returns {*} Result of applying native function | ||
| */ | ||
| /* async */ Object applyNativeFunction(JFunction proc, Frame env) { | ||
| // Not called in Java - JFunction call directly calls native function | ||
| return null; | ||
| var sigArgs = getNativeFunctionArguments(proc); | ||
| // generate the array of arguments for invoking the function - look them up in the environment | ||
| var args = sigArgs.stream().map(sigArg -> { | ||
| return env.lookup(sigArg); | ||
| }).collect(Collectors.toList()); | ||
|
|
||
| var result = proc.call(null, args); | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Get native Object arguments | ||
| * @param {Function} func - Function | ||
| * @returns {*|Array} Native Object arguments | ||
| */ | ||
| List getNativeFunctionArguments(JFunction func) { | ||
| // Not called in Java | ||
| return null; | ||
| List<String> getNativeFunctionArguments(JFunction func) { | ||
| // Java: parameter names are not available, use a, b, c, ... | ||
| var sigArgs = new ArrayList<String>(); | ||
| for (int i=0; i<func.getNumberOfArgs(); i++) | ||
| sigArgs.add("" + (char)('a'+i)); | ||
| return sigArgs; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -2098,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); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -2181,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> { | ||
|
|
@@ -2191,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), | ||
|
|
@@ -2235,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), | ||
|
|
@@ -2243,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), | ||
|
|
@@ -2271,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; | ||
|
|
@@ -2317,8 +2335,16 @@ public Object validate(Object args, Object context) { | |
| return args; | ||
| } | ||
|
|
||
| /** | ||
| * @return the number of declared parameters, -1 if unknown | ||
| */ | ||
| public int getNumberOfArgs() { | ||
| return method != null ? method.getParameterTypes().length : 0; | ||
| 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() : -1; | ||
| } | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bad formatting
closing parenthesis looks like for "if (next instanceof Symbol)" but not