diff --git a/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java b/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java deleted file mode 100644 index 20cccbbbc7..0000000000 --- a/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.rumbledb.compiler; - -import org.rumbledb.context.BuiltinFunction; -import org.rumbledb.context.BuiltinFunctionCatalogue; -import org.rumbledb.context.Name; -import org.rumbledb.expressions.Expression; -import org.rumbledb.expressions.Node; -import org.rumbledb.expressions.postfix.DynamicFunctionCallExpression; -import org.rumbledb.expressions.primary.FunctionCallExpression; -import org.rumbledb.expressions.primary.InlineFunctionExpression; -import org.rumbledb.expressions.primary.NamedFunctionReferenceExpression; -import org.rumbledb.expressions.primary.VariableReferenceExpression; -import org.rumbledb.expressions.scripting.statement.StatementsAndOptionalExpr; -import org.rumbledb.types.SequenceType; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Rewrites direct partial application of builtins, e.g. {@code fn:max(0, ?)}, - * into - * an equivalent inline function, e.g. {@code function($x) { fn:max(0, $x) }}. - *

- * This keeps builtin partial application on the same code path as ordinary - * inline functions. - */ -public class BuiltinPartialApplicationRewriteVisitor extends CloneVisitor { - - private InlineFunctionExpression rewriteBuiltinPartialApplication( - Name functionName, - BuiltinFunction builtin, - List arguments, - Expression sourceExpression - ) { - List parameterTypes = builtin.getSignature().getParameterTypes(); - Map params = new LinkedHashMap<>(); - - /// We will create a new function call to builtin function, but this time - /// replace ? with real parameters - List fullArguments = new ArrayList<>(arguments.size()); - - for (int i = 0; i < arguments.size(); i++) { - Expression currentArgument = arguments.get(i); - if (currentArgument != null) { - /// It was not a ? - fullArguments.add(currentArgument); - continue; - } - - Name parameterName = Name.createVariableInNoNamespace(String.format("param%s", i)); - params.put(parameterName, parameterTypes.get(i)); - VariableReferenceExpression variableReference = new VariableReferenceExpression( - parameterName, - sourceExpression.getMetadata() - ); - variableReference.setActualType(parameterTypes.get(i)); - fullArguments.add(variableReference); - } - - FunctionCallExpression bodyCall = new FunctionCallExpression( - functionName, - fullArguments, - sourceExpression.getMetadata() - ); - StatementsAndOptionalExpr body = new StatementsAndOptionalExpr( - Collections.emptyList(), - bodyCall, - sourceExpression.getMetadata() - ); - return new InlineFunctionExpression( - Collections.emptyList(), - null, - params, - builtin.getSignature().getReturnType(), - body, - sourceExpression.getMetadata() - ); - } - - @Override - public Node visitFunctionCall(FunctionCallExpression expression, Node argument) { - BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(expression.getFunctionIdentifier()); - - if (!expression.isPartialApplication() || builtin == null) { - /// In case of non-partial application or non-builtin function, we still need to - /// keep descending - /// Because a partial builtin function might be in the nested level - /// See qt3 test hof-041 - return super.visitFunctionCall(expression, argument); - } - - List arguments = expression.getArguments() - .stream() - .map(expr -> expr != null ? (Expression) visit(expr, argument) : null) - .collect(Collectors.toList()); - return rewriteBuiltinPartialApplication(expression.getFunctionName(), builtin, arguments, expression); - } - - @Override - public Node visitDynamicFunctionCallExpression(DynamicFunctionCallExpression expression, Node argument) { - List arguments = expression.getArguments() - .stream() - .map(expr -> expr != null ? (Expression) visit(expr, argument) : null) - .collect(Collectors.toList()); - Expression rewrittenMainExpression = (Expression) visit(expression.getMainExpression(), argument); - - if (!(rewrittenMainExpression instanceof NamedFunctionReferenceExpression namedFunctionReference)) { - return super.visitDynamicFunctionCallExpression(expression, argument); - } - - BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(namedFunctionReference.getIdentifier()); - boolean isPartialApplication = arguments.stream().anyMatch(arg -> arg == null); - if (!isPartialApplication || builtin == null) { - return super.visitDynamicFunctionCallExpression(expression, argument); - } - - return rewriteBuiltinPartialApplication( - namedFunctionReference.getIdentifier().getName(), - builtin, - arguments, - expression - ); - } -} diff --git a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java index b2baba266a..db9a830d3d 100644 --- a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java @@ -267,7 +267,9 @@ public StaticContext visitFunctionCall(FunctionCallExpression expression, Static expression.getMetadata() ); } - if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { + if (expression.isPartialApplication()) { + expression.setHighestExecutionMode(ExecutionMode.LOCAL); + } else if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { BuiltinFunction builtinFunction = BuiltinFunctionCatalogue.getBuiltinFunction( expression.getFunctionIdentifier() ); diff --git a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java index 7e8598c4ce..e0886f7a94 100644 --- a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java @@ -21,7 +21,6 @@ import org.rumbledb.exceptions.OurBadException; import org.rumbledb.exceptions.UnexpectedStaticTypeException; import org.rumbledb.exceptions.UnknownFunctionCallException; -import org.rumbledb.exceptions.UnsupportedFeatureException; import org.rumbledb.expressions.AbstractNodeVisitor; import org.rumbledb.expressions.CommaExpression; import org.rumbledb.expressions.Expression; @@ -761,13 +760,6 @@ public StaticContext visitFunctionCall(FunctionCallExpression expression, Static visitDescendants(expression, argument); if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { - if (expression.isPartialApplication()) { - /// This should never be reached because partial application on built-in functions should have been rewritten before - throw new UnsupportedFeatureException( - "Partial application on built-in functions are not supported.", - expression.getMetadata() - ); - } BuiltinFunction builtinFunction = BuiltinFunctionCatalogue.getBuiltinFunction( expression.getFunctionIdentifier() ); diff --git a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java index 4ffec221c1..6627864e48 100644 --- a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java +++ b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java @@ -1330,7 +1330,18 @@ public RuntimeIterator visitFunctionCall(FunctionCallExpression expression, Runt FunctionIdentifier identifier = new FunctionIdentifier(fnName, arity); RuntimeIterator runtimeIterator = null; - if (BuiltinFunctionCatalogue.exists(identifier)) { + if (expression.isPartialApplication()) { + runtimeIterator = new DynamicFunctionCallIterator( + new NamedFunctionRefRuntimeIterator( + identifier, + expression.getStaticContextForRuntime(this.config, this.visitorConfig) + ), + arguments, + expression.getStaticContextForRuntime(this.config, this.visitorConfig) + ); + } + + else if (BuiltinFunctionCatalogue.exists(identifier)) { runtimeIterator = NamedFunctions.getBuiltInFunctionIterator( identifier, arguments, diff --git a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java index aa1ea2f1e7..10460c0901 100644 --- a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java +++ b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java @@ -65,15 +65,6 @@ private static void inferTypes(Module module, RumbleRuntimeConfiguration conf) { private static MainModule applyTypeIndependentOptimizations(MainModule module, RumbleRuntimeConfiguration conf) { MainModule result = module; - if (conf.debug()) { - System.err.println("***************************************"); - System.err.println("Builtin Partial Application Rewrite Visitor"); - System.err.println("***************************************"); - } - result = (MainModule) new BuiltinPartialApplicationRewriteVisitor().visit(result, null); - if (conf.debug()) { - printTree(result, conf); - } // Annotate recursive functions as such if (conf.debug()) { System.err.println("***************************************"); diff --git a/src/main/java/org/rumbledb/context/NamedFunctions.java b/src/main/java/org/rumbledb/context/NamedFunctions.java index a8957937ec..9579837c16 100644 --- a/src/main/java/org/rumbledb/context/NamedFunctions.java +++ b/src/main/java/org/rumbledb/context/NamedFunctions.java @@ -29,13 +29,21 @@ import org.rumbledb.exceptions.DuplicateFunctionIdentifierException; import org.rumbledb.exceptions.ExceptionMetadata; import org.rumbledb.exceptions.OurBadException; -import org.rumbledb.exceptions.UnsupportedFeatureException; import org.rumbledb.exceptions.UnknownFunctionCallException; import org.rumbledb.expressions.ExecutionMode; import org.rumbledb.items.FunctionItem; +import org.rumbledb.items.PartiallyAppliedFunctionItem; +import org.rumbledb.items.PartiallyAppliedFunctionItem.ArgumentBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.DataFrameBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.LocalBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.PlaceholderBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.RddBinding; import org.rumbledb.runtime.RuntimeIterator; import org.rumbledb.runtime.functions.BuiltinFunctionItemCallIterator; +import org.rumbledb.runtime.functions.CapturedFunctionArgumentIterator; +import org.rumbledb.runtime.functions.FunctionCallArgumentCoercion; import org.rumbledb.runtime.functions.FunctionItemCallIterator; +import org.rumbledb.runtime.functions.PartialFunctionCallIterator; import org.rumbledb.runtime.functions.sequences.general.DataFunctionIterator; import org.rumbledb.runtime.typing.AtMostOneItemTypePromotionIterator; import org.rumbledb.runtime.typing.TypePromotionIterator; @@ -44,6 +52,7 @@ import java.io.Serializable; import java.lang.reflect.Constructor; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -52,6 +61,12 @@ public class NamedFunctions implements Serializable, KryoSerializable { private static final long serialVersionUID = 1L; + public record ResolvedFunctionCall( + Item functionItem, + List arguments, + ExecutionMode executionMode) { + } + // two maps for User defined function are needed as execution mode is known at // static analysis phase // but functions items are fully known at runtimeIterator generation @@ -104,7 +119,56 @@ public static RuntimeIterator buildFunctionItemCallIterator( List arguments, boolean isTailOptimization ) { - ExceptionMetadata metadata = callerRuntimeContext.getMetadata(); + if (functionItem instanceof PartiallyAppliedFunctionItem) { + return buildResolvedFunctionItemCallIterator( + resolveFunctionItemCall(functionItem, arguments, callerRuntimeContext), + callerRuntimeContext + ); + } + return buildDirectFunctionItemCallIterator( + functionItem, + callerRuntimeContext, + executionModeForFunctionCall, + arguments, + isTailOptimization + ); + } + + public static RuntimeIterator buildResolvedFunctionItemCallIterator( + ResolvedFunctionCall resolvedCall, + RuntimeStaticContext callerRuntimeContext + ) { + return buildDirectFunctionItemCallIterator( + resolvedCall.functionItem(), + callerRuntimeContext, + resolvedCall.executionMode(), + new ArrayList<>(resolvedCall.arguments()), + false + ); + } + + private static RuntimeIterator buildDirectFunctionItemCallIterator( + Item functionItem, + RuntimeStaticContext callerRuntimeContext, + ExecutionMode executionModeForFunctionCall, + List arguments, + boolean isTailOptimization + ) { + if (isTailOptimization) { + return new PartialFunctionCallIterator( + functionItem, + arguments, + callerRuntimeContext.withExecutionMode(ExecutionMode.LOCAL), + Name.TAIL_CALL_OPTIMIZATION + ); + } + if (arguments.stream().anyMatch(a -> a == null)) { + return new PartialFunctionCallIterator( + functionItem, + arguments, + callerRuntimeContext.withExecutionMode(ExecutionMode.LOCAL) + ); + } SequenceType sequenceType = functionItem.getSignature().getReturnType(); SequenceType innerSequenceType = functionItem.getBodyIterator().getStaticType(); RuntimeStaticContext outerStaticContext = callerRuntimeContext.withStaticType( @@ -118,12 +182,6 @@ public static RuntimeIterator buildFunctionItemCallIterator( ).withExecutionMode(executionModeForFunctionCall); RuntimeIterator functionCallIterator; if (functionItem.isBuiltinFunction()) { - if (arguments.stream().anyMatch(a -> a == null)) { - throw new UnsupportedFeatureException( - "Partial application of builtin named function references is not supported yet.", - metadata - ); - } functionCallIterator = new BuiltinFunctionItemCallIterator( functionItem, arguments, @@ -169,6 +227,89 @@ public static RuntimeIterator buildFunctionItemCallIterator( } } + public static ResolvedFunctionCall resolveFunctionItemCall( + Item functionItem, + List arguments, + RuntimeStaticContext callerRuntimeContext + ) { + Item resolvedFunction = functionItem; + List resolvedArguments = new ArrayList<>(arguments); + while (resolvedFunction instanceof PartiallyAppliedFunctionItem partiallyAppliedFunction) { + FunctionCallArgumentCoercion.validateArity( + resolvedFunction, + resolvedArguments, + callerRuntimeContext.getMetadata() + ); + FunctionCallArgumentCoercion.wrapAccordingToSignature( + resolvedFunction, + resolvedArguments, + callerRuntimeContext + ); + resolvedArguments = expandPartialArguments( + partiallyAppliedFunction, + resolvedArguments, + callerRuntimeContext + ); + resolvedFunction = partiallyAppliedFunction.getTargetFunction(); + } + ExecutionMode executionMode = resolvedArguments.stream().anyMatch(argument -> argument == null) + ? ExecutionMode.LOCAL + : resolveDirectFunctionItemExecutionMode(resolvedFunction, resolvedArguments, callerRuntimeContext); + return new ResolvedFunctionCall(resolvedFunction, resolvedArguments, executionMode); + } + + private static ExecutionMode resolveDirectFunctionItemExecutionMode( + Item functionItem, + List arguments, + RuntimeStaticContext callerRuntimeContext + ) { + if (functionItem.isBuiltinFunction()) { + BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(functionItem.getIdentifier()); + ExecutionMode firstArgumentMode = arguments.isEmpty() || arguments.get(0) == null + ? ExecutionMode.LOCAL + : arguments.get(0).getHighestExecutionMode(); + return BuiltinFunctionExecutionModes.resolve( + builtin, + firstArgumentMode, + callerRuntimeContext.getConfiguration() + ); + } + return functionItem.getBodyIterator().getHighestExecutionMode(); + } + + private static List expandPartialArguments( + PartiallyAppliedFunctionItem functionItem, + List suppliedArguments, + RuntimeStaticContext callerRuntimeContext + ) { + List result = new ArrayList<>(); + int suppliedIndex = 0; + for (ArgumentBinding binding : functionItem.getArgumentBindings()) { + if (binding instanceof PlaceholderBinding) { + result.add(suppliedArguments.get(suppliedIndex++)); + continue; + } + ExecutionMode executionMode; + if (binding instanceof DataFrameBinding) { + executionMode = ExecutionMode.DATAFRAME; + } else if (binding instanceof RddBinding) { + executionMode = ExecutionMode.RDD; + } else if (binding instanceof LocalBinding) { + executionMode = ExecutionMode.LOCAL; + } else { + throw new OurBadException("Unsupported partial-function argument binding."); + } + result.add( + CapturedFunctionArgumentIterator.create( + binding, + callerRuntimeContext.withStaticType(binding.sequenceType()) + .withExecutionMode(executionMode) + ) + ); + } + return result; + } + public void addUserDefinedFunction(Item function, ExceptionMetadata meta) { if (!function.isFunction()) { throw new OurBadException("Only a function item can be added as a user-defined function."); diff --git a/src/main/java/org/rumbledb/items/PartiallyAppliedFunctionItem.java b/src/main/java/org/rumbledb/items/PartiallyAppliedFunctionItem.java new file mode 100644 index 0000000000..0e19cca7a0 --- /dev/null +++ b/src/main/java/org/rumbledb/items/PartiallyAppliedFunctionItem.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rumbledb.items; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import org.apache.spark.api.java.JavaRDD; +import org.rumbledb.api.Item; +import org.rumbledb.context.DynamicContext; +import org.rumbledb.context.FunctionIdentifier; +import org.rumbledb.context.Name; +import org.rumbledb.exceptions.OurBadException; +import org.rumbledb.items.structured.JSoundDataFrame; +import org.rumbledb.types.FunctionSignature; +import org.rumbledb.types.SequenceType; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; + +/** + * Function item produced by partial application. + * + * It stores the original target and an argument binding for every target parameter. Invocation expands + * these bindings and delegates to the ordinary function-item dispatch path. + */ +public class PartiallyAppliedFunctionItem extends FunctionItem { + + private static final long serialVersionUID = 1L; + + public sealed interface ArgumentBinding extends Serializable + permits PlaceholderBinding, LocalBinding, RddBinding, DataFrameBinding { + + SequenceType sequenceType(); + } + + public record PlaceholderBinding(SequenceType sequenceType) implements ArgumentBinding { + + private static final long serialVersionUID = 1L; + } + + public record LocalBinding(SequenceType sequenceType, List value) implements ArgumentBinding { + + private static final long serialVersionUID = 1L; + + public LocalBinding { + value = List.copyOf(value); + } + } + + public record RddBinding(SequenceType sequenceType, JavaRDD value) implements ArgumentBinding { + + private static final long serialVersionUID = 1L; + } + + public record DataFrameBinding(SequenceType sequenceType, JSoundDataFrame value) implements ArgumentBinding { + + private static final long serialVersionUID = 1L; + } + + private Item targetFunction; + private List argumentBindings; + + public PartiallyAppliedFunctionItem() { + super(); + } + + public PartiallyAppliedFunctionItem( + FunctionIdentifier identifier, + List parameterNames, + FunctionSignature signature, + DynamicContext dynamicModuleContext, + Item targetFunction, + List argumentBindings + ) { + super( + identifier, + parameterNames, + signature, + dynamicModuleContext, + getTargetBody(targetFunction), + new HashMap<>(), + new HashMap<>(), + new HashMap<>(), + false + ); + validateBindings(identifier, targetFunction, argumentBindings); + this.targetFunction = targetFunction; + this.argumentBindings = List.copyOf(argumentBindings); + } + + private static org.rumbledb.runtime.RuntimeIterator getTargetBody(Item targetFunction) { + if (targetFunction == null || !targetFunction.isFunction()) { + throw new OurBadException("A partially applied function must have a function target."); + } + return targetFunction.getBodyIterator(); + } + + public Item getTargetFunction() { + return this.targetFunction; + } + + public List getArgumentBindings() { + return this.argumentBindings; + } + + private static void validateBindings( + FunctionIdentifier identifier, + Item targetFunction, + List argumentBindings + ) { + if (argumentBindings.size() != targetFunction.getIdentifier().getArity()) { + throw new OurBadException("Partial-function bindings do not match the target arity."); + } + long placeholderCount = argumentBindings.stream().filter(PlaceholderBinding.class::isInstance).count(); + if (placeholderCount != identifier.getArity()) { + throw new OurBadException("Partial-function placeholders do not match the partial signature."); + } + } + + @Override + public void write(Kryo kryo, Output output) { + super.write(kryo, output); + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream objects = new ObjectOutputStream(bytes); + objects.writeObject(this.targetFunction); + objects.writeObject(this.argumentBindings); + objects.flush(); + byte[] serialized = bytes.toByteArray(); + output.writeInt(serialized.length); + output.writeBytes(serialized); + } catch (IOException e) { + throw new IllegalStateException("Could not serialize partially applied function metadata.", e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void read(Kryo kryo, Input input) { + super.read(kryo, input); + try { + int length = input.readInt(); + byte[] serialized = input.readBytes(length); + ObjectInputStream objects = new ObjectInputStream(new ByteArrayInputStream(serialized)); + this.targetFunction = (Item) objects.readObject(); + this.argumentBindings = List.copyOf((List) objects.readObject()); + validateBindings(getIdentifier(), this.targetFunction, this.argumentBindings); + } catch (IOException | ClassNotFoundException e) { + throw new IllegalStateException("Could not deserialize partially applied function metadata.", e); + } + } +} diff --git a/src/main/java/org/rumbledb/runtime/functions/CapturedFunctionArgumentIterator.java b/src/main/java/org/rumbledb/runtime/functions/CapturedFunctionArgumentIterator.java new file mode 100644 index 0000000000..447748f4c2 --- /dev/null +++ b/src/main/java/org/rumbledb/runtime/functions/CapturedFunctionArgumentIterator.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rumbledb.runtime.functions; + +import org.apache.spark.api.java.JavaRDD; +import org.rumbledb.api.Item; +import org.rumbledb.context.DynamicContext; +import org.rumbledb.context.RuntimeStaticContext; +import org.rumbledb.exceptions.IteratorFlowException; +import org.rumbledb.exceptions.OurBadException; +import org.rumbledb.items.PartiallyAppliedFunctionItem.ArgumentBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.DataFrameBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.LocalBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.RddBinding; +import org.rumbledb.items.structured.JSoundDataFrame; +import org.rumbledb.runtime.HybridRuntimeIterator; + +import java.util.List; + +/** + * Context-independent iterator over a value captured by partial application. + */ +public class CapturedFunctionArgumentIterator extends HybridRuntimeIterator { + + private static final long serialVersionUID = 1L; + + private final List localValue; + private final JavaRDD rddValue; + private final JSoundDataFrame dataFrameValue; + private int index; + + private CapturedFunctionArgumentIterator( + List localValue, + JavaRDD rddValue, + JSoundDataFrame dataFrameValue, + RuntimeStaticContext staticContext + ) { + super(null, staticContext); + this.localValue = localValue; + this.rddValue = rddValue; + this.dataFrameValue = dataFrameValue; + } + + public static CapturedFunctionArgumentIterator create( + ArgumentBinding binding, + RuntimeStaticContext staticContext + ) { + if (binding instanceof LocalBinding local) { + return new CapturedFunctionArgumentIterator(local.value(), null, null, staticContext); + } + if (binding instanceof RddBinding rdd) { + return new CapturedFunctionArgumentIterator(null, rdd.value(), null, staticContext); + } + if (binding instanceof DataFrameBinding dataFrame) { + return new CapturedFunctionArgumentIterator(null, null, dataFrame.value(), staticContext); + } + throw new OurBadException("A placeholder cannot be converted to a captured argument."); + } + + @Override + protected void openLocal() { + this.index = 0; + this.hasNext = !this.localValue.isEmpty(); + } + + @Override + protected boolean hasNextLocal() { + return this.hasNext; + } + + @Override + protected Item nextLocal() { + if (!this.hasNext) { + throw new IteratorFlowException(FLOW_EXCEPTION_MESSAGE, getMetadata()); + } + Item result = this.localValue.get(this.index++); + this.hasNext = this.index < this.localValue.size(); + return result; + } + + @Override + protected void resetLocal() { + openLocal(); + } + + @Override + protected void closeLocal() { + this.index = 0; + } + + @Override + public JavaRDD getRDDAux(DynamicContext context) { + if (this.rddValue != null) { + return this.rddValue; + } + return dataFrameToRDDOfItems(this.dataFrameValue, getMetadata()); + } + + @Override + protected boolean implementsDataFrames() { + return true; + } + + @Override + public JSoundDataFrame getDataFrame(DynamicContext context) { + return this.dataFrameValue; + } +} diff --git a/src/main/java/org/rumbledb/runtime/functions/DynamicFunctionCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/DynamicFunctionCallIterator.java index 9132baba41..52dedac80c 100644 --- a/src/main/java/org/rumbledb/runtime/functions/DynamicFunctionCallIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/DynamicFunctionCallIterator.java @@ -24,9 +24,6 @@ import org.apache.spark.api.java.JavaRDD; import org.rumbledb.api.Item; -import org.rumbledb.context.BuiltinFunction; -import org.rumbledb.context.BuiltinFunctionCatalogue; -import org.rumbledb.context.BuiltinFunctionExecutionModes; import org.rumbledb.context.DynamicContext; import org.rumbledb.context.NamedFunctions; import org.rumbledb.context.RuntimeStaticContext; @@ -226,9 +223,24 @@ private void setFunctionItemAndIteratorWithCurrentContext(DynamicContext context getMetadata() ); } - ExecutionMode calleeExecutionMode = getCalleeExecutionModeForFunctionItemCall(); + if (this.isPartialApplication) { + this.functionCallIterator = NamedFunctions.buildFunctionItemCallIterator( + this.functionItem, + this.staticContext, + ExecutionMode.LOCAL, + this.functionArguments, + false + ); + return; + } + + NamedFunctions.ResolvedFunctionCall resolvedCall = NamedFunctions.resolveFunctionItemCall( + this.functionItem, + this.functionArguments, + this.staticContext + ); if ( - calleeExecutionMode.equals(ExecutionMode.LOCAL) + resolvedCall.executionMode().equals(ExecutionMode.LOCAL) && this.getHighestExecutionMode().equals(ExecutionMode.DATAFRAME) ) { throw new OurBadException( @@ -237,35 +249,12 @@ private void setFunctionItemAndIteratorWithCurrentContext(DynamicContext context getMetadata() ); } - this.functionCallIterator = NamedFunctions.buildFunctionItemCallIterator( - this.functionItem, - this.staticContext, - this.isPartialApplication ? ExecutionMode.LOCAL : calleeExecutionMode, - this.functionArguments, - false + this.functionCallIterator = NamedFunctions.buildResolvedFunctionItemCallIterator( + resolvedCall, + this.staticContext ); } - private ExecutionMode getCalleeExecutionModeForFunctionItemCall() { - if (this.isPartialApplication) { - return ExecutionMode.LOCAL; - } - if (this.functionItem.isBuiltinFunction()) { - BuiltinFunction builtin = - BuiltinFunctionCatalogue.getBuiltinFunction(this.functionItem.getIdentifier()); - // assume that the passed builtin function is valid - ExecutionMode firstArgumentMode = ExecutionMode.LOCAL; - for (RuntimeIterator arg : this.functionArguments) { - if (arg != null) { - firstArgumentMode = arg.getHighestExecutionMode(); - break; - } - } - return BuiltinFunctionExecutionModes.resolve(builtin, firstArgumentMode, getConfiguration()); - } - return this.functionItem.getBodyIterator().getHighestExecutionMode(); - } - @Override public void resetLocal() { this.functionCallIterator.reset(this.currentDynamicContextForLocalExecution); diff --git a/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java index 246ead3fdd..9a9247fcf7 100644 --- a/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java @@ -20,7 +20,6 @@ package org.rumbledb.runtime.functions; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -28,24 +27,13 @@ import org.apache.spark.api.java.JavaRDD; import org.rumbledb.api.Item; import org.rumbledb.context.DynamicContext; -import org.rumbledb.context.FunctionIdentifier; import org.rumbledb.context.Name; import org.rumbledb.context.RuntimeStaticContext; import org.rumbledb.exceptions.IteratorFlowException; -import org.rumbledb.exceptions.OurBadException; -import org.rumbledb.exceptions.UnexpectedTypeException; -import org.rumbledb.expressions.ExecutionMode; -import org.rumbledb.items.FunctionItem; import org.rumbledb.items.structured.JSoundDataFrame; -import org.rumbledb.runtime.ConstantRuntimeIterator; import org.rumbledb.runtime.HybridRuntimeIterator; import org.rumbledb.runtime.RuntimeIterator; -import org.rumbledb.runtime.typing.AtMostOneItemTypePromotionIterator; -import org.rumbledb.runtime.typing.TypePromotionIterator; import org.rumbledb.runtime.update.PendingUpdateList; -import org.rumbledb.types.FunctionSignature; -import org.rumbledb.types.SequenceType; -import org.rumbledb.types.SequenceType.Arity; public class FunctionItemCallIterator extends HybridRuntimeIterator { @@ -55,8 +43,6 @@ public class FunctionItemCallIterator extends HybridRuntimeIterator { private List functionArguments; // calculated fields - private boolean isPartialApplication; - private boolean isTailOptimization; private RuntimeIterator functionBodyIterator; private Item nextResult; private transient DynamicContext dynamicContextForCalls; @@ -70,23 +56,19 @@ public FunctionItemCallIterator( ) { super(null, staticContext); for (RuntimeIterator arg : functionArguments) { - if (arg == null) { - this.isPartialApplication = true; - } else { - this.children.add(arg); - } - } - if (isTailOptimization) { - this.isPartialApplication = true; - this.isTailOptimization = true; + this.children.add(arg); } this.functionItem = functionItem; this.functionArguments = functionArguments; this.functionBodyIterator = null; this.isUpdating = functionItem.getSignature().isUpdating(); - this.validateNumberOfArguments(); - this.wrapArgumentIteratorsWithTypeCheckingIterators(); + FunctionCallArgumentCoercion.validateArity(functionItem, this.functionArguments, getMetadata()); + FunctionCallArgumentCoercion.wrapAccordingToSignature( + functionItem, + this.functionArguments, + staticContext + ); // Prepopulation of the dynamic context (without the parameters) Map> localArgumentValues = new LinkedHashMap<>( @@ -107,155 +89,18 @@ public FunctionItemCallIterator( ); } - private void validateNumberOfArguments() { - if (this.functionItem.getParameterNames().size() != this.functionArguments.size()) { - throw new UnexpectedTypeException( - "Dynamic function " - + this.functionItem.getIdentifier().getName() - + " invoked with incorrect number of arguments. Expected: " - + this.functionItem.getParameterNames().size() - + ", Found: " - + this.functionArguments.size(), - getMetadata() - ); - } - } - - private void wrapArgumentIteratorsWithTypeCheckingIterators() { - if (this.functionItem.getSignature().getParameterTypes() != null) { - for (int i = 0; i < this.functionArguments.size(); i++) { - if ( - this.functionArguments.get(i) != null - && !this.functionItem.getSignature() - .getParameterTypes() - .get(i) - .equals(SequenceType.createSequenceType("item*")) - ) { - SequenceType sequenceType = this.functionItem.getSignature().getParameterTypes().get(i); - ExecutionMode executionMode = this.functionArguments.get(i).getHighestExecutionMode(); - if ( - sequenceType.isEmptySequence() - || sequenceType.getArity().equals(Arity.One) - || sequenceType.getArity().equals(Arity.OneOrZero) - ) { - executionMode = ExecutionMode.LOCAL; - } - RuntimeStaticContext runtimeStaticContext = getRuntimeStaticContext().withStaticType(sequenceType) - .withExecutionMode(executionMode) - .withMetadata(this.functionArguments.get(i).getMetadata()); - if ( - sequenceType.isEmptySequence() - || sequenceType.getArity().equals(Arity.One) - || sequenceType.getArity().equals(Arity.OneOrZero) - ) { - RuntimeIterator typePromotionIterator = new AtMostOneItemTypePromotionIterator( - this.functionArguments.get(i), - sequenceType, - "Invalid argument for " + this.functionItem.getIdentifier().getName() + " function. ", - runtimeStaticContext - ); - this.functionArguments.set(i, typePromotionIterator); - } else { - RuntimeIterator typePromotionIterator = new TypePromotionIterator( - this.functionArguments.get(i), - sequenceType, - "Invalid argument for " + this.functionItem.getIdentifier().getName() + " function. ", - runtimeStaticContext - ); - this.functionArguments.set(i, typePromotionIterator); - } - } - } - } - } - @Override public void openLocal() { - if (this.isPartialApplication) { - this.functionBodyIterator = generatePartiallyAppliedFunction(this.currentDynamicContextForLocalExecution); - } else { - if (this.functionBodyIterator == null) { - this.functionBodyIterator = this.functionItem.getBodyIterator().deepCopy(); - } - this.populateDynamicContextWithArguments( - this.currentDynamicContextForLocalExecution - ); + if (this.functionBodyIterator == null) { + this.functionBodyIterator = this.functionItem.getBodyIterator().deepCopy(); } + this.populateDynamicContextWithArguments( + this.currentDynamicContextForLocalExecution + ); this.functionBodyIterator.open(this.dynamicContextForCalls); setNextResult(); } - /** - * Partial application generates a new function: - * - Supplied parameters are set as NonLocalVariables - * - Argument placeholders form the parameters - * - * @return FunctionRuntimeIterator that contains the newly generated FunctionItem - */ - private RuntimeIterator generatePartiallyAppliedFunction(DynamicContext context) { - Name argName; - RuntimeIterator argIterator; - - Map> localArgumentValues = new LinkedHashMap<>( - this.functionItem.getLocalVariablesInClosure() - ); - Map> RDDArgumentValues = new LinkedHashMap<>( - this.functionItem.getRDDVariablesInClosure() - ); - Map DFArgumentValues = new LinkedHashMap<>( - this.functionItem.getDFVariablesInClosure() - ); - - List partialApplicationParamNames = new ArrayList<>(); - List partialApplicationParamTypes = new ArrayList<>(); - - for (int i = 0; i < this.functionArguments.size(); i++) { - argName = this.functionItem.getParameterNames().get(i); - argIterator = this.functionArguments.get(i); - - if (argIterator == null) { // == ArgumentPlaceholder - partialApplicationParamNames.add(argName); - partialApplicationParamTypes.add(this.functionItem.getSignature().getParameterTypes().get(i)); - } else { - if (argIterator.isDataFrame()) { - DFArgumentValues.put(argName, argIterator.getDataFrame(context)); - } else if (argIterator.isRDDOrDataFrame()) { - RDDArgumentValues.put(argName, argIterator.getRDD(context)); - } else { - localArgumentValues.put(argName, argIterator.materialize(context)); - } - } - } - - Name functionItemName = this.functionItem.getIdentifier().getName(); - if (this.isTailOptimization) { - functionItemName = Name.TAIL_CALL_OPTIMIZATION; - } - FunctionItem partiallyAppliedFunction = new FunctionItem( - new FunctionIdentifier( - functionItemName, - partialApplicationParamNames.size() - ), - partialApplicationParamNames, - new FunctionSignature( - partialApplicationParamTypes, - this.functionItem.getSignature().getReturnType(), - this.functionItem.getSignature().isUpdating() - ), - this.functionItem.getModuleDynamicContext(), - this.functionItem.getBodyIterator(), - localArgumentValues, - RDDArgumentValues, - DFArgumentValues - ); - return new ConstantRuntimeIterator( - partiallyAppliedFunction, - this.staticContext.withStaticType( - SequenceType.createSequenceType("function(*)") - ).withExecutionMode(ExecutionMode.LOCAL).withMetadata(getMetadata()) - ); - } - private void populateDynamicContextWithArguments(DynamicContext context) { Name argName; RuntimeIterator argIterator; @@ -328,12 +173,6 @@ public void setNextResult() { @Override public JavaRDD getRDDAux(DynamicContext dynamicContext) { - if (this.isPartialApplication) { - throw new OurBadException( - "Unexpected program state reached. Partially applied function calls must be evaluated locally." - ); - } - this.populateDynamicContextWithArguments(dynamicContext); this.functionBodyIterator = this.functionItem.getBodyIterator(); return this.functionBodyIterator.getRDD(this.dynamicContextForCalls); @@ -346,12 +185,6 @@ protected boolean implementsDataFrames() { @Override public JSoundDataFrame getDataFrame(DynamicContext dynamicContext) { - if (this.isPartialApplication) { - throw new OurBadException( - "Unexpected program state reached. Partially applied function calls must be evaluated locally." - ); - } - populateDynamicContextWithArguments(dynamicContext); this.functionBodyIterator = this.functionItem.getBodyIterator(); return this.functionBodyIterator.getDataFrame(this.dynamicContextForCalls); diff --git a/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java new file mode 100644 index 0000000000..899db041b4 --- /dev/null +++ b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rumbledb.runtime.functions; + +import org.rumbledb.api.Item; +import org.rumbledb.context.DynamicContext; +import org.rumbledb.context.FunctionIdentifier; +import org.rumbledb.context.Name; +import org.rumbledb.context.RuntimeStaticContext; +import org.rumbledb.items.PartiallyAppliedFunctionItem; +import org.rumbledb.items.PartiallyAppliedFunctionItem.ArgumentBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.DataFrameBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.LocalBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.PlaceholderBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.RddBinding; +import org.rumbledb.runtime.AtMostOneItemLocalRuntimeIterator; +import org.rumbledb.runtime.RuntimeIterator; +import org.rumbledb.types.FunctionSignature; +import org.rumbledb.types.SequenceType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Creates a partially-applied function item by capturing supplied arguments in the closure and + * exposing each placeholder as a parameter of the returned function item. + */ +public class PartialFunctionCallIterator extends AtMostOneItemLocalRuntimeIterator { + + private static final long serialVersionUID = 1L; + private final Item functionItem; + private final List functionArguments; + private final Name functionNameOverride; + + public PartialFunctionCallIterator( + Item functionItem, + List functionArguments, + RuntimeStaticContext staticContext + ) { + this(functionItem, functionArguments, staticContext, null); + } + + public PartialFunctionCallIterator( + Item functionItem, + List functionArguments, + RuntimeStaticContext staticContext, + Name functionNameOverride + ) { + super(null, staticContext); + for (RuntimeIterator arg : functionArguments) { + if (arg != null) { + this.children.add(arg); + } + } + this.functionItem = functionItem; + this.functionArguments = functionArguments; + this.functionNameOverride = functionNameOverride; + + FunctionCallArgumentCoercion.validateArity(functionItem, this.functionArguments, getMetadata()); + FunctionCallArgumentCoercion.wrapAccordingToSignature( + functionItem, + this.functionArguments, + staticContext + ); + } + + @Override + public Item materializeFirstItemOrNull(DynamicContext context) { + List partialApplicationParamNames = new ArrayList<>(); + List partialApplicationParamTypes = new ArrayList<>(); + List argumentBindings = new ArrayList<>(); + + for (int i = 0; i < this.functionArguments.size(); i++) { + Name parameterName = this.functionItem.getParameterNames().get(i); + RuntimeIterator argumentIterator = this.functionArguments.get(i); + SequenceType parameterType = this.functionItem.getSignature().getParameterTypes().get(i); + + if (argumentIterator == null) { + partialApplicationParamNames.add(parameterName); + partialApplicationParamTypes.add(parameterType); + argumentBindings.add(new PlaceholderBinding(parameterType)); + } else if (argumentIterator.isDataFrame()) { + argumentBindings.add( + new DataFrameBinding(parameterType, argumentIterator.getDataFrame(context)) + ); + } else if (argumentIterator.isRDDOrDataFrame()) { + argumentBindings.add(new RddBinding(parameterType, argumentIterator.getRDD(context))); + } else { + argumentBindings.add(new LocalBinding(parameterType, argumentIterator.materialize(context))); + } + } + + return new PartiallyAppliedFunctionItem( + new FunctionIdentifier( + this.functionNameOverride, + partialApplicationParamNames.size() + ), + partialApplicationParamNames, + new FunctionSignature( + partialApplicationParamTypes, + this.functionItem.getSignature().getReturnType(), + this.functionItem.getSignature().isUpdating() + ), + this.functionItem.getModuleDynamicContext(), + this.functionItem, + argumentBindings + ); + } +} diff --git a/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java index b7fdbd85a3..797c9db91c 100644 --- a/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java @@ -37,7 +37,6 @@ public Item materializeFirstItemOrNull(DynamicContext context) { getMetadata() ); } - System.err.println("Item is of type function"); Item functionItem = functionIterator.materializeFirstItemOrNull(context); if (functionItem == null || !(functionItem instanceof FunctionItem)) { throw new OurBadException("Expected argument to be of type function and not be null"); diff --git a/src/test/java/org/rumbledb/items/PartiallyAppliedFunctionItemTest.java b/src/test/java/org/rumbledb/items/PartiallyAppliedFunctionItemTest.java new file mode 100644 index 0000000000..6e8ce7d644 --- /dev/null +++ b/src/test/java/org/rumbledb/items/PartiallyAppliedFunctionItemTest.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rumbledb.items; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import org.junit.Assert; +import org.junit.Test; +import org.rumbledb.api.Item; +import org.rumbledb.config.RumbleRuntimeConfiguration; +import org.rumbledb.context.BuiltinFunction; +import org.rumbledb.context.BuiltinFunctionCatalogue; +import org.rumbledb.context.DynamicContext; +import org.rumbledb.context.FunctionIdentifier; +import org.rumbledb.context.Name; +import org.rumbledb.exceptions.ExceptionMetadata; +import org.rumbledb.items.PartiallyAppliedFunctionItem.ArgumentBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.LocalBinding; +import org.rumbledb.items.PartiallyAppliedFunctionItem.PlaceholderBinding; +import org.rumbledb.types.FunctionSignature; +import org.rumbledb.types.SequenceType; + +import java.util.ArrayList; +import java.util.List; + +public class PartiallyAppliedFunctionItemTest { + + @Test + public void localBindingsAndBindingListAreImmutable() { + List capturedValue = new ArrayList<>(); + capturedValue.add(ItemFactory.getInstance().createIntItem(1)); + LocalBinding binding = new LocalBinding(parameterType(), capturedValue); + capturedValue.add(ItemFactory.getInstance().createIntItem(2)); + Assert.assertEquals(1, binding.value().size()); + + List bindings = new ArrayList<>(); + bindings.add(binding); + PartiallyAppliedFunctionItem partial = createPartial(builtinTarget(), bindings, 0); + bindings.clear(); + Assert.assertEquals(1, partial.getArgumentBindings().size()); + } + + @Test + public void deepCopyPreservesNestedPartialFunctionType() { + PartiallyAppliedFunctionItem inner = createPartial( + builtinTarget(), + List.of(new PlaceholderBinding(parameterType())), + 1 + ); + PartiallyAppliedFunctionItem outer = createPartial( + inner, + List.of(new PlaceholderBinding(parameterType())), + 1 + ); + + FunctionItem copy = outer.deepCopy(); + Assert.assertTrue(copy instanceof PartiallyAppliedFunctionItem); + Item copiedTarget = ((PartiallyAppliedFunctionItem) copy).getTargetFunction(); + Assert.assertTrue(copiedTarget instanceof PartiallyAppliedFunctionItem); + } + + @Test + public void kryoRoundTripPreservesLocalBindings() { + PartiallyAppliedFunctionItem partial = createPartial( + builtinTarget(), + List.of( + new LocalBinding( + parameterType(), + List.of(ItemFactory.getInstance().createIntItem(7)) + ) + ), + 0 + ); + Kryo kryo = new Kryo(); + kryo.setRegistrationRequired(false); + Output output = new Output(4096, -1); + kryo.writeClassAndObject(output, partial); + Input input = new Input(output.toBytes()); + + Object deserialized = kryo.readClassAndObject(input); + Assert.assertTrue(deserialized instanceof PartiallyAppliedFunctionItem); + ArgumentBinding binding = ((PartiallyAppliedFunctionItem) deserialized).getArgumentBindings().get(0); + Assert.assertTrue(binding instanceof LocalBinding); + Assert.assertEquals(7, ((LocalBinding) binding).value().get(0).getIntValue()); + } + + private static PartiallyAppliedFunctionItem createPartial( + Item target, + List bindings, + int arity + ) { + List parameterNames = arity == 0 + ? List.of() + : List.of(Name.createVariableInNoNamespace("$p0")); + List parameterTypes = arity == 0 ? List.of() : List.of(parameterType()); + return new PartiallyAppliedFunctionItem( + new FunctionIdentifier(null, arity), + parameterNames, + new FunctionSignature(parameterTypes, target.getSignature().getReturnType(), false), + target.getModuleDynamicContext(), + target, + bindings + ); + } + + private static FunctionItem builtinTarget() { + RumbleRuntimeConfiguration configuration = new RumbleRuntimeConfiguration(new String[] {}); + DynamicContext context = new DynamicContext(configuration); + FunctionIdentifier identifier = new FunctionIdentifier( + Name.createVariableInDefaultFunctionNamespace("max"), + 1 + ); + BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(identifier); + return FunctionItemFactory.createBuiltinNamedReference( + identifier, + context, + configuration, + ExceptionMetadata.EMPTY_METADATA, + builtin + ); + } + + private static SequenceType parameterType() { + FunctionIdentifier identifier = new FunctionIdentifier( + Name.createVariableInDefaultFunctionNamespace("max"), + 1 + ); + return BuiltinFunctionCatalogue.getBuiltinFunction(identifier).getSignature().getParameterTypes().get(0); + } +} diff --git a/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication1.jq b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication1.jq new file mode 100644 index 0000000000..e4eb53bc33 --- /dev/null +++ b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication1.jq @@ -0,0 +1,7 @@ +(:JIQS: ShouldRun; Output="(3, false)" :) +let $partial := max#1(?) +return +( + $partial((1, 2, 3)), + exists(function-name($partial)) +) diff --git a/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication2.jq b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication2.jq new file mode 100644 index 0000000000..a7a7a9576e --- /dev/null +++ b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplication2.jq @@ -0,0 +1,4 @@ +(:JIQS: ShouldRun; Output="(2, 3, 4)" :) +let $partial := tail(?) +let $partial-again := $partial(?) +return $partial-again(1 to 4) diff --git a/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD1.jq b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD1.jq new file mode 100644 index 0000000000..7ba7b2e853 --- /dev/null +++ b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD1.jq @@ -0,0 +1,3 @@ +(:JIQS: ShouldRun; Output="(2, 3, 4, 5)" :) +let $partial := tail(?) +return $partial(parallelize(1 to 5)) diff --git a/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD2.jq b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD2.jq new file mode 100644 index 0000000000..c59d7b948c --- /dev/null +++ b/src/test/resources/test_files/runtime/FunctionPartialApplication/PartialBuiltinFunctionApplicationRDD2.jq @@ -0,0 +1,3 @@ +(:JIQS: ShouldRun; Output="(1, 9, 2, 3, 4)" :) +let $partial := insert-before(parallelize(1 to 4), ?, 9) +return $partial(2)