net.sf.saxon.expr.UserFunctionCall Maven / Gradle / Ivy
Show all versions of saxon9 Show documentation
package net.sf.saxon.expr;
import net.sf.saxon.event.SequenceReceiver;
import net.sf.saxon.evpull.EmptyEventIterator;
import net.sf.saxon.evpull.EventIterator;
import net.sf.saxon.instruct.InstructionDetails;
import net.sf.saxon.instruct.UserFunction;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.om.ValueRepresentation;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trace.InstructionInfo;
import net.sf.saxon.trace.InstructionInfoProvider;
import net.sf.saxon.trace.Location;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.*;
/**
* This class represents a call to a user-defined function in the stylesheet or query.
*/
public class UserFunctionCall extends FunctionCall implements InstructionInfoProvider {
private SequenceType staticType;
private UserFunction function;
private boolean tailCall = false;
// indicates only that this is a tail call, not necessarily a recursive tail call
private boolean confirmed = false;
// A functionCall is confirmed if the function being called has been located. Generally in this
// case the value of 'function' will be non-null; but in XSLT it is possible to look-ahead to confirm
// a function binding before the relevant function is actually compiled.
private int[] argumentEvaluationModes = null;
/**
* Create a function call to a user-written function in a query or stylesheet
*/
public UserFunctionCall() {}
/**
* Set the static type
* @param type the static type
*/
public void setStaticType(SequenceType type) {
staticType = type;
}
/**
* Create the reference to the function to be called
* @param compiledFunction the function being called
*/
public void setFunction(UserFunction compiledFunction) {
function = compiledFunction;
confirmed = true;
}
/**
* Check the function call against the declared function signature
* @param compiledFunction the function being called
* @param visitor an expression visitor
*/
public void checkFunctionCall(UserFunction compiledFunction,
ExpressionVisitor visitor) throws XPathException {
int n = compiledFunction.getNumberOfArguments();
for (int i=0; i 1) {
m = ExpressionTool.MAKE_MEMO_CLOSURE;
}
argumentEvaluationModes[i] = m;
}
}
}
/**
* Add a representation of this expression to a PathMap. The PathMap captures a map of the nodes visited
* by an expression in a source tree.
*
* The default implementation of this method assumes that an expression does no navigation other than
* the navigation done by evaluating its subexpressions, and that the subexpressions are evaluated in the
* same context as the containing expression. The method must be overridden for any expression
* where these assumptions do not hold. For example, implementations exist for AxisExpression, ParentExpression,
* and RootExpression (because they perform navigation), and for the doc(), document(), and collection()
* functions because they create a new navigation root. Implementations also exist for PathExpression and
* FilterExpression because they have subexpressions that are evaluated in a different context from the
* calling expression.
*
* @param pathMap the PathMap to which the expression should be added
* @param pathMapNodeSet
* @return the pathMapNode representing the focus established by this expression, in the case where this
* expression is the first operand of a path expression or filter expression. For an expression that does
* navigation, it represents the end of the arc in the path map that describes the navigation route. For other
* expressions, it is the same as the input pathMapNode.
*/
public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) {
return addExternalFunctionCallToPathMap(pathMap, pathMapNodeSet);
}
/**
* Mark tail-recursive calls on stylesheet functions. This marks the function call as tailRecursive if
* if is a call to the containing function, and in this case it also returns "true" to the caller to indicate
* that a tail call was found.
*/
public int markTailFunctionCalls(StructuredQName qName, int arity) {
tailCall = true;
return (getFunctionName().equals(qName) &&
arity == getNumberOfArguments() ? 2 : 1);
}
// TODO: attempt to establish whether the function is capable of creating new nodes. This
// enables non-creative functions to be moved out of loops. The problem is how to achieve this
// without looping in the case of recursive functions. A simple solution might be to go only
// one level deep: if the body of a function is known (without analysing function calls) to be
// non-creative, then all calls on that function can be marked as non-creative. Note also that
// a function is creative if one of its arguments is creative and the result of the function
// depends on the identity of that argument.
public int getImplementationMethod() {
if (Cardinality.allowsMany(getCardinality())) {
return ITERATE_METHOD | PROCESS_METHOD;
} else {
return EVALUATE_METHOD;
}
}
/**
* Call the function, returning the value as an item. This method will be used
* only when the cardinality is zero or one. If the function is tail recursive,
* it returns an Object representing the arguments to the next (recursive) call
*/
public Item evaluateItem(XPathContext c) throws XPathException {
ValueRepresentation val = callFunction(c);
return Value.asItem(val);
}
/**
* Call the function, returning an iterator over the results. (But if the function is
* tail recursive, it returns an iterator over the arguments of the recursive call)
*/
public SequenceIterator iterate(XPathContext c) throws XPathException {
ValueRepresentation result = callFunction(c);
return Value.getIterator(result);
}
/**
* This is the method that actually does the function call
* @param c the dynamic context
* @return the result of the function
* @throws XPathException if dynamic errors occur
*/
private ValueRepresentation callFunction(XPathContext c) throws XPathException {
ValueRepresentation[] actualArgs = evaluateArguments(c);
if (tailCall) {
((XPathContextMajor)c).requestTailCall(function, actualArgs);
return EmptySequence.getInstance();
}
XPathContextMajor c2 = c.newCleanContext();
c2.setOrigin(this);
c2.setTemporaryOutputState(true);
try {
return function.call(actualArgs, c2);
} catch (StackOverflowError err) {
throw new XPathException("Too many nested function calls. May be due to infinite recursion.", this);
} catch (NullPointerException err) {
if (function == null) {
throw new NullPointerException("Unbound function call " +
function.getFunctionName().getDisplayName());
} else {
throw err;
}
}
}
/**
* Process the function call in push mode
* @param context the XPath dynamic context
* @throws XPathException
*/
public void process(XPathContext context) throws XPathException {
ValueRepresentation[] actualArgs = evaluateArguments(context);
if (tailCall) {
((XPathContextMajor)context).requestTailCall(function, actualArgs);
} else {
SequenceReceiver out = context.getReceiver();
XPathContextMajor c2 = context.newCleanContext();
c2.setReceiver(out);
c2.setOrigin(this);
function.process(actualArgs, c2);
}
}
/**
* Process the function call in pull mode
* @param context the XPath dynamic context
* @throws XPathException
*/
public EventIterator iterateEvents(XPathContext context) throws XPathException {
ValueRepresentation[] actualArgs = evaluateArguments(context);
if (tailCall) {
((XPathContextMajor)context).requestTailCall(function, actualArgs);
return EmptyEventIterator.getInstance();
} else {
SequenceReceiver out = context.getReceiver();
XPathContextMajor c2 = context.newCleanContext();
c2.setReceiver(out);
c2.setOrigin(this);
return function.iterateEvents(actualArgs, c2);
}
}
private ValueRepresentation[] evaluateArguments(XPathContext c) throws XPathException {
int numArgs = argument.length;
ValueRepresentation[] actualArgs = new ValueRepresentation[numArgs];
if (argumentEvaluationModes == null) {
// should have been done at compile time
computeArgumentEvaluationModes();
}
for (int i=0; i 1 && actualArgs[i] instanceof Closure && !(actualArgs[i] instanceof MemoClosure)) {
actualArgs[i] = ((Closure)actualArgs[i]).reduce();
}
}
return actualArgs;
}
/**
* Call the function dynamically. For this to be possible, the static arguments of the function call
* must have been set up as SuppliedParameterReference objects. The actual arguments are placed on the
* callee's stack, and the type conversion takes place "in situ".
* @param suppliedArguments the values to be used for the arguments of the function
* @param context the dynamic evaluation context
* @return the result of evaluating the function
*/
public ValueRepresentation dynamicCall(ValueRepresentation[] suppliedArguments, XPathContext context) throws XPathException {
ValueRepresentation[] convertedArgs = new ValueRepresentation[suppliedArguments.length];
XPathContextMajor c2 = context.newCleanContext();
c2.setOrigin(this);
c2.setCaller(context);
c2.openStackFrame(suppliedArguments.length);
for (int i=0; i