All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.SystemFunctionCall Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 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.expr;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.Outputter;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.instruct.AnalyzeString;
import net.sf.saxon.expr.instruct.Block;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.Error;
import net.sf.saxon.functions.*;
import net.sf.saxon.functions.registry.BuiltInFunctionSet;
import net.sf.saxon.ma.map.MapFunctionSet;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NamespaceUri;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.pattern.NodeSetPattern;
import net.sf.saxon.pattern.Pattern;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.IntegerValue;

/**
 * A call to a system-defined function (specifically, a function implemented as an instance
 * of {@link net.sf.saxon.functions.SystemFunction})
 */
public class SystemFunctionCall extends StaticFunctionCall implements Negatable {

    public SystemFunctionCall(SystemFunction target, Expression[] arguments) {
        super(target, arguments);
    }

    /**
     * Set the retained static context
     *
     * @param rsc the static context to be retained
     */
    @Override
    public void setRetainedStaticContext(RetainedStaticContext rsc) {
        super.setRetainedStaticContext(rsc);
        getTargetFunction().setRetainedStaticContext(rsc);
    }

    /**
     * Pre-evaluate a function at compile time. Functions that do not allow
     * pre-evaluation, or that need access to context information, can prevent early
     * evaluation by setting the LATE bit in the function properties.
     *
     * @param visitor an expression visitor
     * @return the result of the early evaluation, or the original expression, or potentially
     * a simplified expression
     * @throws net.sf.saxon.trans.XPathException if evaluation fails
     */
    @Override
    public Expression preEvaluate(ExpressionVisitor visitor) throws XPathException {
        SystemFunction target = getTargetFunction();
        if ((target.getDetails().properties & BuiltInFunctionSet.LATE) == 0) {
            return super.preEvaluate(visitor);
        } else {
            // Early evaluation of this function is suppressed
            return this;
        }
    }

    /**
     * Type-check the expression. This also calls preEvaluate() to evaluate the function
     * if all the arguments are constant; functions that do not require this behavior
     * can override the preEvaluate method.
     *
     * @param visitor     the expression visitor
     * @param contextInfo information about the type of the context item
     */
    @Override
    public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
        typeCheckChildren(visitor, contextInfo);
        checkFunctionCall(getTargetFunction(), visitor);
        // Give the function an opportunity to use the type information now available
        getTargetFunction().supplyTypeInformation(visitor, contextInfo, getArguments());
        if ((getTargetFunction().getDetails().properties & BuiltInFunctionSet.LATE) == 0) {
            return preEvaluateIfConstant(visitor);
        }
        //allocateArgumentEvaluators(getArguments());
        return this;
    }

//    public void allocateArgumentEvaluators(Expression[] arguments) {
//        int lastExplicitArg = Math.min(arguments.length, getTargetFunction().getDetails().argumentTypes.length)-1;
//        for (int i = 0; i < arguments.length; i++) {
//            Expression arg = arguments[i];
//            argumentEvaluators[i] = Elaborator.makeElaborator(arg).lazily(false);
//        }
//    }

    @Override
    public SystemFunction getTargetFunction() {
        return (SystemFunction) super.getTargetFunction();
    }

    /**
     * Compute the dependencies of an expression, as the union of the
     * dependencies of its subexpressions. (This is overridden for path expressions
     * and filter expressions, where the dependencies of a subexpression are not all
     * propogated). This method should be called only once, to compute the dependencies;
     * after that, getDependencies should be used.
     *
     * @return the depencies, as a bit-mask
     */
    @Override
    public int getIntrinsicDependencies() {
        int properties = getTargetFunction().getDetails().properties;
        int dep = 0;
        if ((properties & BuiltInFunctionSet.LATE) != 0) {
            dep = StaticProperty.DEPENDS_ON_RUNTIME_ENVIRONMENT;
        }
        if ((properties & BuiltInFunctionSet.FOCUS) != 0) {
            if ((properties & BuiltInFunctionSet.CDOC) != 0) {
                dep |= StaticProperty.DEPENDS_ON_CONTEXT_DOCUMENT;
            }
            if ((properties & BuiltInFunctionSet.CITEM) != 0) {
                dep |= StaticProperty.DEPENDS_ON_CONTEXT_ITEM;
            }
            if ((properties & BuiltInFunctionSet.POSN) != 0) {
                dep |= StaticProperty.DEPENDS_ON_POSITION;
            }
            if ((properties & BuiltInFunctionSet.LAST) != 0) {
                dep |= StaticProperty.DEPENDS_ON_LAST;
            }
        }
        if ((properties & BuiltInFunctionSet.BASE) != 0) {
            dep |= StaticProperty.DEPENDS_ON_STATIC_CONTEXT;
        }
        if ((properties & BuiltInFunctionSet.DCOLL) != 0) {
            dep |= StaticProperty.DEPENDS_ON_STATIC_CONTEXT;
        }
        if (isCallOn(RegexGroup.class) || isCallOn(CurrentMergeGroup.class) || isCallOn(CurrentMergeKey.class)) {
            dep |= StaticProperty.DEPENDS_ON_CURRENT_GROUP;
        }
        return dep;
    }

    /**
     * Compute the static cardinality of this expression
     *
     * @return the computed cardinality, as one of the values {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_ONE},
     * {@link net.sf.saxon.expr.StaticProperty#EXACTLY_ONE}, {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ONE_OR_MORE},
     * {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_MORE}
     */
    @Override
    protected int computeCardinality() {
        return getTargetFunction().getCardinality(getArguments());
    }

    /**
     * Compute the special properties of this expression. These properties are denoted by a bit-significant
     * integer, possible values are in class {@link net.sf.saxon.expr.StaticProperty}. The "special" properties are properties
     * other than cardinality and dependencies, and most of them relate to properties of node sequences, for
     * example whether the nodes are in document order.
     *
     * @return the special properties, as a bit-significant integer
     */
    @Override
    protected int computeSpecialProperties() {
        return getTargetFunction().getSpecialProperties(getArguments());
    }

    /**
     * Return the estimated cost of evaluating an expression. This is a very crude measure based
     * on the syntactic form of the expression (we have no knowledge of data values). We take
     * the cost of evaluating a simple scalar comparison or arithmetic expression as 1 (one),
     * and we assume that a sequence has length 5. The resulting estimates may be used, for
     * example, to reorder the predicates in a filter expression so cheaper predicates are
     * evaluated first.
     */
    @Override
    public int getNetCost() {
        return getTargetFunction().getNetCost();
    }

    @Override
    public Expression getScopingExpression() {
        if (isCallOn(RegexGroup.class)) {
            Expression parent = getParentExpression();
            while (parent != null) {
                if (parent instanceof AnalyzeString) {
                    return parent;
                }
                parent = parent.getParentExpression();
            }
            return null;
        } else {
            return super.getScopingExpression();
        }
    }

    /**
     * Ask whether the expression can be lifted out of a loop, assuming it has no dependencies
     * on the controlling variable/focus of the loop
     *
     * @param forStreaming true if we are optimizing for streamed evaluation
     */
    @Override
    public boolean isLiftable(boolean forStreaming) {
        // xsl:map-entry is not liftable when streaming because of the special streamability
        // rules for xsl:map; similarly XPath map constructor expressions.
        // The tests for current-merge-group/key were added to fix bug 3652 - it seems
        // an inelegant solution because it's being handled differently from other context
        // dependencies, but it works.
        return super.isLiftable(forStreaming) &&
                !isCallOn(CurrentMergeGroup.class) && !isCallOn(CurrentMergeKey.class) &&
                (!forStreaming || !isCallOn(MapFunctionSet.MapEntry.class));
    }

    @Override
    public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
        int properties = getTargetFunction().getDetails().properties;
        if ((properties & BuiltInFunctionSet.CTRL) != 0) {
             // Mechanism devised for saxon:unindexed: don't optimize the arguments first
            Expression sfo = getTargetFunction().makeOptimizedFunctionCall(visitor, contextInfo, getArguments());
            if (sfo != null) {
                sfo.setParentExpression(getParentExpression());
                ExpressionTool.copyLocationInfo(this, sfo);
//                if (sfo instanceof SystemFunctionCall) {
//                    ((SystemFunctionCall) sfo).allocateArgumentEvaluators(((SystemFunctionCall) sfo).getArguments());
//                }
                return sfo;
            }
        }

        Expression sf = super.optimize(visitor, contextInfo);
        if (sf == this) {
            // Give the function an opportunity to regenerate the function call, with more information about
            // the types of the arguments than was previously available
            Expression sfo = getTargetFunction().makeOptimizedFunctionCall(visitor, contextInfo, getArguments());
            if (sfo != null) {
                sfo.setParentExpression(getParentExpression());
                ExpressionTool.copyLocationInfo(this, sfo);
//                if (sfo instanceof SystemFunctionCall) {
//                    ((SystemFunctionCall) sfo).allocateArgumentEvaluators(((SystemFunctionCall) sfo).getArguments());
//                }
                return sfo;
            }
        }
        Optimizer opt = visitor.obtainOptimizer();
        if (sf instanceof SystemFunctionCall && opt.isOptionSet(OptimizerOptions.CONSTANT_FOLDING)) {
            // If any arguments are known to be empty, pre-evaluate the result
            BuiltInFunctionSet.Entry details = ((SystemFunctionCall) sf).getTargetFunction().getDetails();
            if ((details.properties & BuiltInFunctionSet.UO) != 0) {
                // First argument does not need to be in any particular order
                setArg(0, getArg(0).unordered(true, visitor.isOptimizeForStreaming()));
            }
            if (getArity() <= details.resultIfEmpty.length) {
                // the condition eliminates concat, which is a special case.
                for (int i = 0; i < getArity(); i++) {
                    if (Literal.isEmptySequence(getArg(i)) && details.resultIfEmpty[i] != null) {
                        return Literal.makeLiteral(details.resultIfEmpty[i].materialize(), this);
                    }
                }
            }
            //((SystemFunctionCall) sf).allocateArgumentEvaluators(((SystemFunctionCall) sf).getArguments());
        }
        return sf;
    }

    @Override
    public boolean isVacuousExpression() {
        return isCallOn(Error.class);
    }

    /**
     * Determine the data type of the expression, if possible. All expression return
     * sequences, in general; this method determines the type of the items within the
     * sequence, assuming that (a) this is known in advance, and (b) it is the same for
     * all items in the sequence.
     * 

This 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 getTargetFunction().getResultItemType(getArguments()); } /** * Copy an expression. This makes a deep copy. * @param rebindings variables that need to be re-bound * @return the copy of the original expression */ @Override public Expression copy(RebindingMap rebindings) { Expression[] args = new Expression[getArity()]; for (int i = 0; i < args.length; i++) { args[i] = getArg(i).copy(rebindings); } SystemFunction target = getTargetFunction(); if (target instanceof StatefulSystemFunction) { target = ((StatefulSystemFunction) target).copy(); } return target.makeFunctionCall(args); } /** * For an expression that returns an integer or a sequence of integers, get * a lower and upper bound on the values of the integers that may be returned, from * static analysis. The default implementation returns null, meaning "unknown" or * "not applicable". Other implementations return an array of two IntegerValue objects, * representing the lower and upper bounds respectively. The values * UNBOUNDED_LOWER and UNBOUNDED_UPPER are used by convention to indicate that * the value may be arbitrarily large. The values MAX_STRING_LENGTH and MAX_SEQUENCE_LENGTH * are used to indicate values limited by the size of a string or the size of a sequence. * * @return the lower and upper bounds of integer values in the result, or null to indicate * unknown or not applicable. */ //@Override @Override public IntegerValue[] getIntegerBounds() { SystemFunction fn = getTargetFunction(); if ((fn.getDetails().properties & BuiltInFunctionSet.FILTER) != 0) { return getArg(0).getIntegerBounds(); } return fn.getIntegerBounds(); } /** * Check whether this specific instance of the expression is negatable * * @param th the TypeHierarchy (in case it's needed) * @return true if it is */ @Override public boolean isNegatable(TypeHierarchy th) { return isCallOn(NotFn.class) || isCallOn(BooleanFn.class) || isCallOn(Empty.class) || isCallOn(Exists.class); } /** * Create an expression that returns the negation of this expression * * @return the negated expression * @throws UnsupportedOperationException if isNegatable() returns false */ @Override public Expression negate() { SystemFunction fn = getTargetFunction(); if (fn instanceof NotFn) { Expression arg = getArg(0); if (arg.getItemType() == BuiltInAtomicType.BOOLEAN && arg.getCardinality() == StaticProperty.EXACTLY_ONE) { return arg; } else { return SystemFunction.makeCall("boolean", getRetainedStaticContext(), arg); } } else if (fn instanceof BooleanFn) { return SystemFunction.makeCall("not", getRetainedStaticContext(), getArg(0)); } else if (fn instanceof Exists) { return SystemFunction.makeCall("empty", getRetainedStaticContext(), getArg(0)); } else if (fn instanceof Empty) { return SystemFunction.makeCall("exists", getRetainedStaticContext(), getArg(0)); } throw new UnsupportedOperationException(); } /** * Replace this expression by a simpler expression that delivers the results without regard * to order. * * @param retainAllNodes set to true if the result must contain exactly the same nodes as the * original; set to false if the result can eliminate (or introduce) duplicates. * @param forStreaming set to true if the result is to be optimized for streaming * @return an expression that delivers the same nodes in a more convenient order * @throws net.sf.saxon.trans.XPathException if the rewrite fails */ @Override public Expression unordered(boolean retainAllNodes, boolean forStreaming) throws XPathException { SystemFunction fn = getTargetFunction(); if (fn instanceof Reverse) { return getArg(0); } if (fn instanceof TreatFn) { setArg(0, getArg(0).unordered(retainAllNodes, forStreaming)); } return this; } /** * 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 the PathMapNodeSet to which the paths embodied in this expression should be added * @return the pathMapNodeSet representing the points in the source document that are both reachable by this * expression, and that represent possible results of this 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. */ @Override public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) { if (isCallOn(Doc.class) || isCallOn(DocumentFn.class) || isCallOn(CollectionFn.class)) { getArg(0).addToPathMap(pathMap, pathMapNodeSet); return new PathMap.PathMapNodeSet(pathMap.makeNewRoot(this)); } else if (isCallOn(KeyFn.class)) { return ((KeyFn) getTargetFunction()).addToPathMap(pathMap, pathMapNodeSet); } else { return super.addToPathMap(pathMap, pathMapNodeSet); } } /** * Convert this expression to an equivalent XSLT pattern * * @param config the Saxon configuration * @return the equivalent pattern * @throws net.sf.saxon.trans.XPathException if conversion is not possible */ @Override public Pattern toPattern(Configuration config) throws XPathException { SystemFunction fn = getTargetFunction(); if (fn instanceof Root_1) { if (getArg(0) instanceof ContextItemExpression || (getArg(0) instanceof ItemChecker && ((ItemChecker) getArg(0)).getBaseExpression() instanceof ContextItemExpression)) { return new NodeSetPattern(this); } } return super.toPattern(config); } // @Override // public Sequence[] evaluateArguments(XPathContext context) throws XPathException { // OperandArray operanda = getOperanda(); // int numArgs = operanda.getNumberOfOperands(); // Sequence[] actualArgs = new Sequence[numArgs]; // for (int i = 0; i < numArgs; i++) { // actualArgs[i] = argumentEvaluators[i].evaluate(context); // } // return actualArgs; // } // @Override // public void resetLocalStaticProperties() { // super.resetLocalStaticProperties(); // if (argumentEvaluators != null) { // allocateArgumentEvaluators(getArguments()); // } // } @Override public void process(Outputter output, XPathContext context) throws XPathException { makeElaborator().elaborateForPush().processLeavingTail(output, context); } @Override public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { return super.call(context, arguments); } /** * Get a name identifying the kind of expression, in terms meaningful to a user. * * @return a name identifying the kind of expression, in terms meaningful to a user. * The name will always be in the form of a lexical XML QName, and should match the name used * in explain() output displaying the expression. */ @Override public String getExpressionName() { return "sysFuncCall"; } /** * Determine whether this is an updating expression as defined in the XQuery update specification * * @return true if this is an updating expression */ @Override public boolean isUpdatingExpression() { return getTargetFunction() instanceof Put; } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. * * @param out the destination of the output */ @Override public void export(ExpressionPresenter out) throws XPathException { if (getFunctionName().hasURI(NamespaceUri.FN)) { out.startElement("fn", this); final String localPart = getFunctionName().getLocalPart(); out.emitAttribute("name", localPart); getTargetFunction().exportAttributes(out); if (localPart.equals("concat") && "JS".equals(out.getOptions().target) && out.getOptions().targetVersion == 2 && getArity() == 1 && getArg(0) instanceof Block) { // We've reduced concat to a single sequence-valued argument; now we need to spread it out to multiple // arguments. See bug #5383 for (Operand o : getArg(0).operands()) { if (o.getChildExpression() instanceof Literal) { for (Item it : ((Literal)o.getChildExpression()).getGroundedValue().asIterable()) { Literal.exportValue(it, out); } } else { o.getChildExpression().export(out); } } } else { for (Operand o : operands()) { o.getChildExpression().export(out); } } getTargetFunction().exportAdditionalArguments(this, out); out.endElement(); } else { // Function was implemented as an IntegratedFunctionCall in 9.7 and we retain the same export format out.startElement("ifCall", this); out.emitAttribute("name", getFunctionName()); out.emitAttribute("type", getTargetFunction().getFunctionItemType().getResultType().toAlphaCode()); getTargetFunction().exportAttributes(out); for (Operand o : operands()) { o.getChildExpression().export(out); } getTargetFunction().exportAdditionalArguments(this, out); out.endElement(); } } /** * Subclass representing a system function call that has been optimized; this overrides the * optimize() method to do nothing, thus ensuring that optimization converges. */ public abstract static class Optimized extends SystemFunctionCall { public Optimized(SystemFunction target, Expression[] arguments) { super(target, arguments); } @Override public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException { return this; // prevent infinite optimization } } /** * Make an elaborator for this expression * * @return a suitable elaborator */ @Override public Elaborator getElaborator() { SystemFunction fn = getTargetFunction(); Elaborator fnElaborator = fn.getElaborator(); //noinspection ReplaceNullCheck if (fnElaborator != null) { return fnElaborator; } else { return new SystemFunctionCallElaborator(); } } /** * Elaborator for a system function call, used in cases where the specific function call has no custom support */ public static class SystemFunctionCallElaborator extends FunctionCallElaborator { @Override public void setExpression(Expression expr) { super.setExpression(expr); allocateArgumentEvaluators((FunctionCall) expr, false); } public PullEvaluator elaborateForPull() { final SystemFunctionCall expr = (SystemFunctionCall) getExpression(); final SystemFunction fn = expr.getTargetFunction(); switch (argumentEvaluators.length) { case 0: return context -> { try { return fn.call(context, StackFrame.EMPTY_ARRAY_OF_SEQUENCE).iterate(); } catch (XPathException err) { err.maybeSetContext(context); err.maybeSetLocation(expr.getLocation()); throw err; } }; case 1: return context -> { try { return fn.call(context, new Sequence[]{argumentEvaluators[0].evaluate(context)}).iterate(); } catch (XPathException err) { err.maybeSetContext(context); err.maybeSetLocation(expr.getLocation()); throw err; } }; case 2: return context -> { try { return fn.call(context, new Sequence[]{ argumentEvaluators[0].evaluate(context), argumentEvaluators[1].evaluate(context) }).iterate(); } catch (XPathException err) { err.maybeSetContext(context); err.maybeSetLocation(expr.getLocation()); throw err; } }; default: return context -> { try { return fn.call(context, evaluateArguments(context)).iterate(); } catch (XPathException err) { err.maybeSetContext(context); err.maybeSetLocation(expr.getLocation()); throw err; } }; } } @Override public ItemEvaluator elaborateForItem() { final SystemFunctionCall expr = (SystemFunctionCall) getExpression(); final SystemFunction fn = expr.getTargetFunction(); switch (argumentEvaluators.length) { case 0: return context -> fn.call(context, StackFrame.EMPTY_ARRAY_OF_SEQUENCE).head(); case 1: return context -> fn.call(context, new Sequence[]{ argumentEvaluators[0].evaluate(context) }).head(); case 2: return context -> fn.call(context, new Sequence[]{ argumentEvaluators[0].evaluate(context), argumentEvaluators[1].evaluate(context) }).head(); default: return context -> fn.call(context, evaluateArguments(context)).head(); } } @Override public PushEvaluator elaborateForPush() { final SystemFunctionCall expr = (SystemFunctionCall) getExpression(); final SystemFunction fn = expr.getTargetFunction(); if (fn instanceof PushableFunction) { return (output, context) -> { Sequence[] actualArgs = evaluateArguments(context); try { ((PushableFunction) fn).process(output, context, actualArgs); } catch (XPathException e) { e.maybeSetLocation(expr.getLocation()); e.maybeSetContext(context); e.maybeSetFailingExpression(expr); throw e; } return null; }; } else { return super.elaborateForPush(); } } @Override public UpdateEvaluator elaborateForUpdate() { final SystemFunctionCall expr = (SystemFunctionCall) getExpression(); if (expr.isVacuousExpression()) { // typically, a call on fn:error PullEvaluator eval = elaborateForPull(); return (context, pul) -> { eval.iterate(context).next(); }; } else { throw new UnsupportedOperationException("Expression " + expr.toShortString() + " is not an updating expression"); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy