net.sf.saxon.functions.hof.UserFunctionReference Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2020 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.functions.hof;
import net.sf.saxon.Controller;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.expr.parser.ContextItemStaticInfo;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.expr.parser.RebindingMap;
import net.sf.saxon.functions.AbstractFunction;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.*;
import net.sf.saxon.query.AnnotationList;
import net.sf.saxon.style.StylesheetPackage;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.AtomicValue;
/**
* A UserFunctionReference is an expression in the form local:f#1 where local:f is a user-defined function.
* Evaluating the expression returns a function item. The reason that UserFunctionReference is treated
* as an expression rather than a value is that the binding to the actual function may be deferred, for
* example if the function is overridden in a different package.
*/
public class UserFunctionReference extends Expression
implements ComponentInvocation, UserFunctionResolvable, Callable {
private final SymbolicName functionName;
private UserFunction nominalTarget;
private int bindingSlot = -1;
private int optimizeCounter = 0;
private int typeCheckCounter = 0;
public UserFunctionReference(UserFunction target) {
this.nominalTarget = target;
this.functionName = target.getSymbolicName();
}
public UserFunctionReference(SymbolicName name) {
this.functionName = name;
}
@Override
public void setFunction(UserFunction function) {
if (!function.getSymbolicName().equals(functionName)) {
throw new IllegalArgumentException("Function name does not match");
}
this.nominalTarget = function;
}
@Override
public Expression simplify() throws XPathException {
// if this is an inline function, simplify the body of that function now
if (nominalTarget.getFunctionName().hasURI(NamespaceConstant.ANONYMOUS) && typeCheckCounter == 0) {
// Prevent recursive simplification
typeCheckCounter++;
nominalTarget.setBody(nominalTarget.getBody().simplify());
typeCheckCounter--;
}
return this;
}
/**
* Perform type checking of an expression and its subexpressions. This is the second phase of
* static optimization.
* This checks statically that the operands of the expression have
* the correct type; if necessary it generates code to do run-time type checking or type
* conversion. A static type error is reported only if execution cannot possibly succeed, that
* is, if a run-time type error is inevitable. The call may return a modified form of the expression.
* This method is called after all references to functions and variables have been resolved
* to the declaration of the function or variable. However, the types of such functions and
* variables may not be accurately known if they have not been explicitly declared.
*
* @param visitor an expression visitor
* @param contextInfo Information available statically about the context item: whether it is (possibly)
* absent; its static type; its streaming posture.
* @return the original expression, rewritten to perform necessary run-time type checks,
* and to perform other type-related optimizations
* @throws XPathException if an error is discovered during this phase
* (typically a type error)
*/
@Override
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
// if this is an inline function, typecheck that function now
//System.err.println("typeCheck " + this);
if (nominalTarget.getFunctionName().hasURI(NamespaceConstant.ANONYMOUS) && typeCheckCounter == 0) {
// Prevent recursive type-checking: test case -s:misc-HigherOrderFunctions -t:xqhof2
typeCheckCounter++;
nominalTarget.typeCheck(visitor);
typeCheckCounter--;
}
return this;
}
/**
* Perform optimisation of an expression and its subexpressions. This is the third and final
* phase of static optimization.
* This method is called after all references to functions and variables have been resolved
* to the declaration of the function or variable, and after all type checking has been done.
*
* @param visitor an expression visitor
* @param contextInfo the static type of "." at the point where this expression is invoked.
* The parameter is set to null if it is known statically that the context item will be undefined.
* If the type of the context item is not known statically, the argument is set to
* {@link Type#ITEM_TYPE}
* @return the original expression, rewritten if appropriate to optimize execution
* @throws XPathException if an error is discovered during this phase
* (typically a type error)
*/
@Override
public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
// if this is an inline function, optimize that function now
//System.err.println("optimize " + this);
if (nominalTarget.getFunctionName().hasURI(NamespaceConstant.ANONYMOUS) && optimizeCounter == 0) {
// Prevent recursive optimization: test case -s:misc-HigherOrderFunctions -t:xqhof2 ; and bug #5054
optimizeCounter++;
Expression o;
o = nominalTarget.getBody().optimize(visitor, ContextItemStaticInfo.ABSENT);
nominalTarget.setBody(o);
SlotManager slotManager = visitor.getConfiguration().makeSlotManager();
for (int i=0; iThis method should always return a result, though it may be the best approximation
* that is available at the time.
*
* @return a value such as Type.STRING, Type.BOOLEAN, Type.NUMBER,
* Type.NODE, or Type.ITEM (meaning not known at compile time)
*/
@Override
public ItemType getItemType() {
return nominalTarget.getFunctionItemType();
}
/**
* Get the static type of the expression as a UType, following precisely the type
* inference rules defined in the XSLT 3.0 specification.
*
* @param contextItemType the static type of the context item
* @return the static item type of the expression according to the XSLT 3.0 defined rules
*/
@Override
public UType getStaticUType(UType contextItemType) {
return UType.FUNCTION;
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
* @param rebindings variables that need to be re-bound
*/
@Override
public Expression copy(RebindingMap rebindings) {
UserFunctionReference ref = new UserFunctionReference(nominalTarget);
ref.optimizeCounter = optimizeCounter;
ref.typeCheckCounter = typeCheckCounter;
return ref;
}
/**
* Evaluate an expression as a single item. This always returns either a single Item or
* null (denoting the empty sequence). No conversion is done. This method should not be
* used unless the static type of the expression is a subtype of "item" or "item?": that is,
* it should not be called if the expression may return a sequence. There is no guarantee that
* this condition will be detected.
*
* @param context The context in which the expression is to be evaluated
* @return the node or atomic value that results from evaluating the
* expression; or null to indicate that the result is an empty
* sequence
* @throws net.sf.saxon.trans.XPathException if any dynamic error occurs evaluating the
* expression
*/
@Override
public Function evaluateItem(XPathContext context) throws XPathException {
if (bindingSlot == -1) {
return new BoundUserFunction(this, nominalTarget, nominalTarget.getDeclaringComponent(), context.getController());
} else {
Component targetComponent = context.getTargetComponent(bindingSlot);
return new BoundUserFunction(this, (UserFunction) targetComponent.getActor(), targetComponent, context.getController());
}
}
/**
* Call the Callable.
*
* @param context the dynamic evaluation context
* @param arguments the values of the arguments, supplied as Sequences.
* Generally it is advisable, if calling iterate() to process a supplied sequence, to
* call it only once; if the value is required more than once, it should first be converted
* to a {@link GroundedValue} by calling the utility method
* SequenceTool.toGroundedValue().
* If the expected value is a single item, the item should be obtained by calling
* Sequence.head(): it cannot be assumed that the item will be passed as an instance of
* {@link Item} or {@link AtomicValue}.
* It is the caller's responsibility to perform any type conversions required
* to convert arguments to the type expected by the callee. An exception is where
* this Callable is explicitly an argument-converting wrapper around the original
* Callable.
* @return the result of the evaluation, in the form of a Sequence. It is the responsibility
* of the callee to ensure that the type of result conforms to the expected result type.
* @throws XPathException if a dynamic error occurs during the evaluation of the expression
*/
@Override
public Function call(XPathContext context, Sequence[] arguments) throws XPathException {
return evaluateItem(context);
}
@Override
public String getExpressionName() {
return "UserFunctionReference";
}
public String toString() {
return getFunctionName().getEQName() + "#" + getArity();
}
@Override
public String toShortString() {
return getFunctionName().getDisplayName() + "#" + getArity();
}
@Override
public void export(ExpressionPresenter out) throws XPathException {
ExpressionPresenter.ExportOptions options = (ExpressionPresenter.ExportOptions) out.getOptions();
if ("JS".equals(options.target) && options.targetVersion == 1){
throw new XPathException("Higher-order functions are not available in Saxon-JS v1.*",
"XTSE3540", getLocation());
}
if (nominalTarget.getDeclaringComponent() == null) {
// This happens for an inline function declared within a static expression, e.g. one
// that is bound to a static global variable. There is no separate component registered for
// such a function, so we expand it inline
out.startElement("inlineFn");
nominalTarget.export(out);
out.endElement();
} else {
StylesheetPackage rootPackage = options.rootPackage;
StylesheetPackage containingPackage = nominalTarget.getDeclaringComponent().getContainingPackage();
if (rootPackage != null && !(rootPackage == containingPackage || rootPackage.contains(containingPackage))) {
throw new XPathException("Cannot export a package containing a reference to a user-defined function ("
+ toShortString()
+ ") that is not present in the package being exported");
}
out.startElement("ufRef");
out.emitAttribute("name", nominalTarget.getFunctionName());
out.emitAttribute("arity", nominalTarget.getArity() + "");
out.emitAttribute("bSlot", "" + getBindingSlot());
out.endElement();
}
}
/**
* A BoundUserFunction represents a user-defined function seen as a component. A single source-level
* XSLT function may be the actor in several different components (in different stylesheet packages).
* Although the code of the function is identical in each case, the bindings to other stylesheet components
* may be different.
*/
public static class BoundUserFunction extends AbstractFunction implements ContextOriginator {
private final ExportAgent agent;
private final Function function;
private final Component component;
private final Controller controller; // retained in case a function is returned from a query or stylesheet
public BoundUserFunction(ExportAgent agent, Function function, Component component, Controller controller) {
this.agent = agent;
this.function = function;
this.component = component;
this.controller = controller;
}
public Function getTargetFunction() {
return function;
}
public Controller getController() {
return controller;
}
@Override
public XPathContext makeNewContext(XPathContext oldContext, ContextOriginator originator) {
if (controller.getConfiguration() != oldContext.getConfiguration()) {
throw new IllegalStateException("A function created under one Configuration cannot be called under a different Configuration");
}
XPathContextMajor c2;
c2 = controller.newXPathContext();
c2.setTemporaryOutputState(StandardNames.XSL_FUNCTION);
c2.setCurrentOutputUri(null);
c2.setCurrentComponent(component);
c2.setResourceResolver(oldContext.getResourceResolver());
c2.setOrigin(originator);
return function.makeNewContext(c2, originator);
}
@Override
public Sequence call(XPathContext context, Sequence[] args) throws XPathException {
XPathContext c2 = function.makeNewContext(context, this);
if (c2 instanceof XPathContextMajor && component != null) {
((XPathContextMajor) c2).setCurrentComponent(component);
}
return function.call(c2, args);
}
@Override
public FunctionItemType getFunctionItemType() {
return function.getFunctionItemType();
}
@Override
public AnnotationList getAnnotations() {
return function.getAnnotations();
}
@Override
public StructuredQName getFunctionName() {
return function.getFunctionName();
}
@Override
public int getArity() {
return function.getArity();
}
@Override
public String getDescription() {
return function.getDescription();
}
@Override
public void export(ExpressionPresenter out) throws XPathException {
agent.export(out);
}
}
}
// Copyright (c) 2018-2022 Saxonica Limited