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

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

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.expr.elab.*;
import net.sf.saxon.expr.oper.OperandArray;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.registry.BuiltInFunctionSet;
import net.sf.saxon.ma.arrays.ArrayFunctionSet;
import net.sf.saxon.ma.arrays.ArrayItemType;
import net.sf.saxon.ma.map.MapFunctionSet;
import net.sf.saxon.ma.map.MapType;
import net.sf.saxon.om.FunctionItem;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

/**
 * This class implements the function fn:apply(), which is a standard function in XQuery 3.1.
 * The fn:apply function is also used internally to implement dynamic function calls.
 */

public class DynamicFunctionCall extends Expression {

    private final Operand targetFunction;
    private final OperandArray suppliedArguments;

    public DynamicFunctionCall(Expression fn, List args) {
        targetFunction = new Operand(this, fn, OperandRole.INSPECT);
        suppliedArguments = new OperandArray(this, args.toArray(new Expression[]{}));
    }

    /**
     * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
     * This method indicates which of these methods is provided directly. The other methods will always be available
     * indirectly, using an implementation that relies on one of the other methods.
     *
     * @return the implementation method, for example {@link #ITERATE_METHOD} or {@link #EVALUATE_METHOD} or
     * {@link #PROCESS_METHOD}
     */
    @Override
    public int getImplementationMethod() {
        return ITERATE_METHOD;
    }

    /**
     * Get the return type
     * @return the best available item type that the function will return
     */

    @Override
    public ItemType getItemType() {
        // Item type of the result is the same as that of the supplied function
        ItemType fnType = targetFunction.getChildExpression().getItemType();
        if (fnType instanceof MapType) {
            return ((MapType)fnType).getValueType().getPrimaryType();
        } else if (fnType instanceof ArrayItemType) {
            return ((ArrayItemType) fnType).getMemberType().getPrimaryType();
        } else if (fnType instanceof FunctionItemType) {
            return ((FunctionItemType) fnType).getResultType().getPrimaryType();
        } else if (fnType instanceof AnyFunctionType) {
            return AnyItemType.getInstance();
        } else {
            return AnyItemType.getInstance();
        }
    }

    /**
     * Compute the static cardinality of this expression
     *
     * @return the computed cardinality, as one of the values {@link StaticProperty#ALLOWS_ZERO_OR_ONE},
     * {@link StaticProperty#EXACTLY_ONE}, {@link StaticProperty#ALLOWS_ONE_OR_MORE},
     * {@link StaticProperty#ALLOWS_ZERO_OR_MORE}. May also return {@link StaticProperty#ALLOWS_ZERO} if
     * the result is known to be an empty sequence, or {@link StaticProperty#ALLOWS_MANY} if
     * if is known to return a sequence of length two or more.
     */
    @Override
    protected int computeCardinality() {
        ItemType fnType = targetFunction.getChildExpression().getItemType();
        if (fnType instanceof MapType) {
            return Cardinality.union(
                    ((MapType) fnType).getValueType().getCardinality(),
                    StaticProperty.ALLOWS_ZERO);
        } else if (fnType instanceof ArrayItemType) {
            return ((ArrayItemType) fnType).getMemberType().getCardinality();
        } else if (fnType instanceof FunctionItemType) {
            return ((FunctionItemType) fnType).getResultType().getCardinality();
        } else {
            return StaticProperty.ALLOWS_ZERO_OR_MORE;
        }
    }

    public int getArity() {
        return suppliedArguments.getNumberOfOperands();
    }

    /**
     * Get the immediate sub-expressions of this expression, with information about the relationship
     * of each expression to its parent expression. Default implementation
     * works off the results of iterateSubExpressions()
     *
     * 

If the expression is a Callable, then it is required that the order of the operands * returned by this function is the same as the order of arguments supplied to the corresponding * call() method.

* * @return an iterator containing the sub-expressions of this expression */ @Override public Iterable operands() { List allOperands = new ArrayList<>(getArity()+1); allOperands.add(targetFunction); for (int i=0; iThis 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 { typeCheckChildren(visitor, contextInfo); TypeChecker tc = visitor.getConfiguration().getTypeChecker(false); Supplier roleSupplier0 = () -> new RoleDiagnostic(RoleDiagnostic.DYNAMIC_FUNCTION, targetFunction.getChildExpression().toShortString(), 0); targetFunction.setChildExpression(tc.staticTypeCheck( targetFunction.getChildExpression(), SequenceType.SINGLE_FUNCTION, roleSupplier0, visitor)); if (getArity() == 1) { Expression target = targetFunction.getChildExpression(); if (target.getItemType() instanceof MapType) { // Convert $map($key) to map:get($map, $key) // This improves streamability analysis - see accumulator-053 return makeGetCall(visitor, MapFunctionSet.getInstance(31), contextInfo); } else if (target.getItemType() instanceof ArrayItemType) { // Convert $array($key) to array:get($array, $key) return makeGetCall(visitor, ArrayFunctionSet.getInstance(31), contextInfo); } } return this; } /** * Create a call on map:get() or array:get() if the function is known to be a map or array * @param visitor the expression visitor * @param fnSet the map or array function set as appropriate * @param contextInfo static information about the context item * @return the map:get() or array:get() function call * @throws XPathException if anything goes wrong. */ private Expression makeGetCall(ExpressionVisitor visitor, BuiltInFunctionSet fnSet, ContextItemStaticInfo contextInfo) throws XPathException { Expression target = targetFunction.getChildExpression(); Expression key = suppliedArguments.getOperandExpression(0); Expression getter = fnSet.makeFunction("get", 2).makeFunctionCall(target, key); getter.setRetainedStaticContext(target.getRetainedStaticContext()); // Use custom diagnostics for type errors on the argument of the call (bug 4772) TypeChecker tc = visitor.getConfiguration().getTypeChecker(visitor.getStaticContext().isInBackwardsCompatibleMode()); if (fnSet == MapFunctionSet.getInstance(31)) { Supplier role = () -> new RoleDiagnostic(RoleDiagnostic.MISC, "key value supplied when calling a map as a function", 0); ((SystemFunctionCall) getter).setArg(1, tc.staticTypeCheck(key, SequenceType.SINGLE_ATOMIC, role, visitor)); } else { Supplier role = () -> new RoleDiagnostic(RoleDiagnostic.MISC, "subscript supplied when calling an array as a function", 0); ((SystemFunctionCall) getter).setArg(1, tc.staticTypeCheck(key, SequenceType.SINGLE_INTEGER, role, visitor)); } return getter.typeCheck(visitor, contextInfo); } /** * Return an Iterator to iterate over the values of a sequence. The value of every * expression can be regarded as a sequence, so this method is supported for all * expressions. This default implementation handles iteration for expressions that * return singleton values: for non-singleton expressions, the subclass must * provide its own implementation. * * @param context supplies the context for evaluation * @return a SequenceIterator that can be used to iterate over the result * of the expression * @throws XPathException if any dynamic error occurs evaluating the * expression */ @Override public SequenceIterator iterate(XPathContext context) throws XPathException { return makeElaborator().elaborateForPull().iterate(context); } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. * * @param out the expression presenter used to display the structure * @throws XPathException if the export fails, for example if an expression is found that won't work * in the target environment. */ @Override public void export(ExpressionPresenter out) throws XPathException { if ("JS".equals(out.getOptions().target) && out.getOptions().targetVersion == 2) { // for backwards compatibility, output a call on saxon:apply out.startElement("ifCall", this); out.emitAttribute("name", "Q{http://saxon.sf.net/}apply"); out.emitAttribute("type", "*"); if (targetFunction.getChildExpression() instanceof Literal) { FunctionItem f = (FunctionItem)(((Literal)targetFunction.getChildExpression()).getGroundedValue()); if (f.getFunctionName() != null) { out.emitAttribute("dyn", f.getFunctionName().getEQName() + "#" + f.getArity()); } } targetFunction.getChildExpression().export(out); out.startSubsidiaryElement("arrayBlock"); for (Operand o : suppliedArguments) { o.getChildExpression().export(out); } out.endSubsidiaryElement(); out.endElement(); } else { out.startElement("dynCall", this); targetFunction.getChildExpression().export(out); for (Operand o : suppliedArguments) { o.getChildExpression().export(out); } out.endElement(); } } /** * Copy an expression. This makes a deep copy. * * @param rebindings a mutable list of (old binding, new binding) pairs * that is used to update the bindings held in any * @return the copy of the original expression */ @Override public Expression copy(RebindingMap rebindings) { Expression f = targetFunction.getChildExpression().copy(rebindings); ArrayList args = new ArrayList<>(getArity()); for (Operand o : suppliedArguments) { args.add(o.getChildExpression().copy(rebindings)); } return new DynamicFunctionCall(f, args); } /** * Get the properties of this object to be included in trace messages, by supplying * the property values to a supplied consumer function * * @param consumer the function to which the properties should be supplied, as (property name, * value) pairs. */ @Override public void gatherProperties(BiConsumer consumer) { // no action } /** * Make an elaborator for this expression * * @return an appropriate {@link Elaborator} */ @Override public Elaborator getElaborator() { return new DynamicFunctionCallElaborator(); } private static class DynamicFunctionCallElaborator extends PullElaborator { @Override public PullEvaluator elaborateForPull() { DynamicFunctionCall expr = (DynamicFunctionCall)getExpression(); TypeHierarchy th = getConfiguration().getTypeHierarchy(); SequenceEvaluator[] argEvaluators = new SequenceEvaluator[expr.getArity()]; for (int i=0; i= 40; return context -> { FunctionItem fn = (FunctionItem) functionEvaluator.eval(context); FunctionItemType fit = fn.getFunctionItemType(); if (fn.getArity() != argEvaluators.length) { String errorCode = "XPTY0004"; throw new XPathException( "Number of arguments required for dynamic call to " + fn.getDescription() + " is " + fn.getArity() + "; number supplied = " + argEvaluators.length, errorCode) .asTypeError() .withXPathContext(context) .withLocation(expr.getLocation()); } Sequence[] argValues = new Sequence[argEvaluators.length]; if (fit == AnyFunctionType.ANY_FUNCTION) { for (int i = 0; i < argEvaluators.length; i++) { argValues[i] = argEvaluators[i].evaluate(context); } } else { for (int i = 0; i < argEvaluators.length; i++) { SequenceType expected = fit.getArgumentTypes()[i]; Sequence actual = argEvaluators[i].evaluate(context); if (!expected.equals(SequenceType.ANY_SEQUENCE)) { Supplier role; role = () -> new RoleDiagnostic(RoleDiagnostic.FUNCTION, fn.getDescription(), 0); actual = th.applyFunctionConversionRules( actual, expected, role, Loc.NONE); } argValues[i] = actual; } } XPathContext c2 = fn.makeNewContext(context, null); if (!is40) { c2.setCurrentOutputUri(null); if (c2 instanceof XPathContextMajor) { ((XPathContextMajor) c2).setCurrentRegexIterator(null); } } Sequence rawResult = fn.call(c2, argValues); if (fn.isTrustedResultType()) { // trust system functions to return a result of the correct type return rawResult.iterate(); } else { // Check the result of the function Supplier resultRole = () -> new RoleDiagnostic(RoleDiagnostic.FUNCTION_RESULT, "fn:apply", -1); return th.applyFunctionConversionRules( rawResult, fit.getResultType(), resultRole, Loc.NONE).iterate(); } }; } } /** * Produce a short string identifying the expression for use in error messages * * @return a short string, sufficient to identify the expression */ @Override public String toShortString() { StringBuilder sb = new StringBuilder(targetFunction.getChildExpression().toShortString()) .append('('); for (Operand op : suppliedArguments) { sb.append(op.getChildExpression().toShortString()).append(','); } sb.setCharAt(sb.length()-1, ')'); return sb.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy