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

net.sf.saxon.expr.instruct.LocalParam 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.instruct;

import net.sf.saxon.expr.*;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ErrorType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.IntegerValue;
import net.sf.saxon.value.SequenceType;

import java.util.function.Supplier;

/**
 * The compiled form of an xsl:param element within a template in an XSLT stylesheet.
 * 

The xsl:param element in XSLT has mandatory attribute name and optional attribute select. It can also * be specified as required="yes" or required="no".

*

This is used only for parameters to XSLT templates. For function calls, the caller of the function * places supplied arguments onto the callee's stackframe and the callee does not need to do anything. * Global parameters (XQuery external variables) are handled using {@link GlobalParam}.

*

The LocalParam class is also used to represent parameters with the saxon:iterate instruction

* *

Changed in Saxon 9.8 to combine the previously-separate LocalParamSetter and LocalParam classes into one

*/ public final class LocalParam extends Instruction implements LocalBinding { private Operand conversionOp = null; private static final int REQUIRED = 4; private static final int TUNNEL = 8; private static final int IMPLICITLY_REQUIRED = 16; // a parameter that is required because the fallback // value is not a valid instance of the type. private int properties = 0; private Operand selectOp = null; private StructuredQName variableQName; private SequenceType requiredType; private int slotNumber = -999; private int referenceCount = 10; /** * Set the expression to which this variable is bound * * @param select the initializing expression */ public void setSelectExpression(Expression select) { if (select != null) { if (selectOp == null) { selectOp = new Operand(this, select, OperandRole.NAVIGATE); } else { selectOp.setChildExpression(select); } } else { selectOp = null; } //evaluator = null; } /** * Get the expression to which this variable is bound * * @return the initializing expression */ public Expression getSelectExpression() { return selectOp == null ? null : selectOp.getChildExpression(); } /** * Set the required type of this variable * * @param required the required type */ public void setRequiredType(SequenceType required) { requiredType = required; } /** * Get the required type of this variable * * @return the required type */ @Override public SequenceType getRequiredType() { return requiredType; } /** * Indicate that this variable represents a required parameter * * @param requiredParam true if this is a required parameter */ public void setRequiredParam(boolean requiredParam) { if (requiredParam) { properties |= REQUIRED; } else { properties &= ~REQUIRED; } } /** * Indicate that this variable represents a parameter that is implicitly required (because there is no * usable default value) * * @param requiredParam true if this is an implicitly required parameter */ public void setImplicitlyRequiredParam(boolean requiredParam) { if (requiredParam) { properties |= IMPLICITLY_REQUIRED; } else { properties &= ~IMPLICITLY_REQUIRED; } } /** * Indicate whether this variable represents a tunnel parameter * * @param tunnel true if this is a tunnel parameter */ public void setTunnel(boolean tunnel) { if (tunnel) { properties |= TUNNEL; } else { properties &= ~TUNNEL; } } /** * Set the nominal number of references to this variable * * @param refCount the nominal number of references */ public void setReferenceCount(int refCount) { referenceCount = refCount; } // /** // * Get the evaluation mode of the variable // * // * @return the evaluation mode (a constant in {@link ExpressionTool} // */ // // public int getEvaluationMode() { // if (evaluationMode == ExpressionTool.UNDECIDED) { // if (referenceCount == FilterExpression.FILTERED) { // evaluationMode = ExpressionTool.MAKE_INDEXED_VARIABLE; // } else { // evaluationMode = ExpressionTool.lazyEvaluationMode(getSelectExpression()); // } // } // return evaluationMode; // } /** * Get the cardinality of the result of this instruction. An xsl:variable instruction returns nothing, so the * type is empty. * * @return the empty cardinality. */ @Override public int getCardinality() { return StaticProperty.EMPTY; } @Override public boolean isAssignable() { return false; } @Override public boolean isGlobal() { return false; } /** * If this is a local variable held on the local stack frame, return the corresponding slot number. * In other cases, return -1. */ @Override public int getLocalSlotNumber() { return slotNumber; } /** * Ask whether this variable represents a required parameter * * @return true if this is a required parameter */ public boolean isRequiredParam() { return (properties & REQUIRED) != 0; } /** * Ask whether this variable represents a parameter that is implicitly required, because there is no usable * default value * * @return true if this variable is an implicitly required parameter */ public boolean isImplicitlyRequiredParam() { return (properties & IMPLICITLY_REQUIRED) != 0; } /** * Ask whether this variable represents a tunnel parameter * * @return true if this is a tunnel parameter */ public boolean isTunnelParam() { return (properties & TUNNEL) != 0; } @Override public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException { Expression e2 = super.typeCheck(visitor, contextItemType); if (e2 != this) { return e2; } checkAgainstRequiredType(visitor); return this; } public void computeEvaluationMode() { // if (getSelectExpression() != null) { // if (referenceCount == FilterExpression.FILTERED) { // final Optimizer optimizer = getConfiguration().obtainOptimizer(); // PullEvaluator pullEval = Elaborator.makeElaborator(getSelectExpression()).elaborateForPull(); // evaluator = context -> optimizer.makeIndexedValue(pullEval.iterate(context)); // } else { // evaluator = Elaborator.makeElaborator(getSelectExpression()).lazily(referenceCount > 1); // } // } } /** * 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 * local variable references that are copied. * @return the copy of the original expression */ @Override public LocalParam copy(RebindingMap rebindings) { LocalParam p2 = new LocalParam(); if (conversionOp != null) { assert getConversion() != null; p2.setConversion(getConversion().copy(rebindings)); } // p2.conversionEvaluator = conversionEvaluator; p2.properties = properties; if (selectOp != null) { assert getSelectExpression() != null; p2.setSelectExpression(getSelectExpression().copy(rebindings)); } p2.variableQName = variableQName; p2.requiredType = requiredType; p2.slotNumber = slotNumber; p2.referenceCount = referenceCount; // p2.evaluator = evaluator; return p2; } @Override public void addReference(VariableReference ref, boolean isLoopingReference) { } /** * Check the select expression against the required type. * * @param visitor an expression visitor * @throws XPathException if the check fails */ public void checkAgainstRequiredType(ExpressionVisitor visitor) throws XPathException { // Note, in some cases we are doing this twice. Supplier role = () -> new RoleDiagnostic(RoleDiagnostic.VARIABLE, variableQName.getDisplayName(), 0); SequenceType r = requiredType; Expression select = getSelectExpression(); if (r != null && select != null) { // check that the expression is consistent with the required type select = visitor.getConfiguration().getTypeChecker(false).staticTypeCheck(select, requiredType, role, visitor); } } // // /** // * Evaluate the variable. That is, // * get the value of the select expression if present or the content // * of the element otherwise, either as a tree or as a sequence // * // * @param context the XPath dynamic context // * @return the result of evaluating the variable // * @throws net.sf.saxon.trans.XPathException // * if evaluation of the select expression fails // * with a dynamic error // */ // public Sequence getSelectValue(XPathContext context) throws XPathException { // Expression select = getSelectExpression(); // if (select == null) { // throw new AssertionError("Internal error: No select expression"); // // The value of the variable is a sequence of nodes and/or atomic values // } else if (select instanceof Literal) { // // fast path for common case // return ((Literal)select).getGroundedValue(); // } else { // // There is a select attribute: do a lazy evaluation of the expression, // // which will already contain any code to force conversion to the required type. // int savedOutputState = context.getTemporaryOutputState(); // context.setTemporaryOutputState(StandardNames.XSL_WITH_PARAM); // Sequence result; // if (evaluator == null) { // evaluator = Elaborator.makeElaborator(select).lazily(true); // } // result = evaluator.evaluate(context); // context.setTemporaryOutputState(savedOutputState); // return result; // } // } /** * Get the slot number allocated to this variable * * @return the slot number, that is the position allocated to the variable on its stack frame */ public int getSlotNumber() { return slotNumber; } /** * Set the slot number of this variable * * @param s the slot number, that is, the position allocated to this variable on its stack frame */ public void setSlotNumber(int s) { slotNumber = s; } /** * Set the name of the variable * * @param s the name of the variable (a QName) */ public void setVariableQName(StructuredQName s) { variableQName = s; } /** * Get the name of this variable * * @return the name of this variable (a QName) */ @Override public StructuredQName getVariableQName() { return variableQName; } /** * Define a conversion that is to be applied to the supplied parameter value. * * @param convertor The expression to be applied. This performs type checking, * and the basic conversions implied by function calling rules, for example * numeric promotion, atomization, and conversion of untyped atomic values to * a required type. The conversion uses the actual parameter value as input, * referencing it using a VariableReference. The argument can be null to indicate * that no conversion is required. */ public void setConversion(Expression convertor) { if (convertor != null) { if (conversionOp == null) { conversionOp = new Operand(this, convertor, OperandRole.SINGLE_ATOMIC); } //conversionEvaluator = Elaborator.makeElaborator(convertor).eagerly(); } else { conversionOp = null; } } /** * Get the conversion expression * * @return the expression used to convert the value to the required type, * or null if there is none */ /*@Nullable*/ public Expression getConversion() { return conversionOp == null ? null : conversionOp.getChildExpression(); } //public SequenceEvaluator getConversionEvaluator() { // return conversionEvaluator; //} /** * Get the name of this instruction for diagnostic and tracing purposes * @return the integer name code */ @Override public int getInstructionNameCode() { return StandardNames.XSL_PARAM; } /** * Get all the XPath expressions associated with this instruction * (in XSLT terms, the expression present on attributes of the instruction, * as distinct from the child instructions in a sequence construction) * @return an iterator over the subexpressions */ @Override public Iterable operands() { return operandSparseList(selectOp, conversionOp); } /** * If the variable is bound to an integer, get the minimum and maximum possible values. * Return null if unknown or not applicable */ @Override public IntegerValue[] getIntegerBoundsForVariable() { return null; } /** * Evaluate the variable */ @Override public Sequence evaluateVariable(XPathContext c) { return c.evaluateLocalVariable(slotNumber); } /** * Check if parameter is compatible with another * * @param other - the LocalParam object to compare * @return result of the compatibility check */ public boolean isCompatible(LocalParam other) { return getVariableQName().equals(other.getVariableQName()) && getRequiredType().equals(other.getRequiredType()) && isTunnelParam() == other.isTunnelParam(); } /** * 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 compiling streamable code */ @Override public boolean isLiftable(boolean forStreaming) { return false; } /** * Ask whether this expression is, or contains, the binding of a given variable * * @param binding the variable binding * @return true if this expression is the variable binding (for example a ForExpression * or LetExpression) or if it is a FLWOR expression that binds the variable in one of its * clauses. */ @Override public boolean hasVariableBinding(Binding binding) { return this == binding; } /** * Get the item type of the items returned by evaluating this instruction * * @return the static item type of the instruction */ /*@NotNull*/ @Override public ItemType getItemType() { return ErrorType.getInstance(); } /** * Get the cardinality of the sequence returned by evaluating this instruction * * @return the static cardinality */ @Override protected int computeCardinality() { return StaticProperty.ALLOWS_ZERO_OR_MORE; } /** * Get the static properties of this expression (other than its type). The result is * bit-signficant. These properties are used for optimizations. In general, if * property bit is set, it is true, but if it is unset, the value is unknown. * * @return a set of flags indicating static properties of this expression */ @Override protected int computeSpecialProperties() { return StaticProperty.HAS_SIDE_EFFECTS; } /** * Determine whether this instruction creates new nodes. * This implementation returns a default value of false * * @return true if the instruction creates new nodes (or if it can't be proved that it doesn't) */ @Override public boolean mayCreateNewNodes() { return false; } /** * 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 "param"; } /** * 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() { return "$" + getVariableQName().getDisplayName(); } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. */ @Override public void export(ExpressionPresenter out) throws XPathException { out.startElement("param", this); out.emitAttribute("name", getVariableQName()); out.emitAttribute("slot", "" + getSlotNumber()); String flags = getFlags(); if (!flags.isEmpty()) { out.emitAttribute("flags", flags); } if (getRequiredType() != SequenceType.ANY_SEQUENCE) { out.emitAttribute("as", getRequiredType().toAlphaCode()); } if (getSelectExpression() != null) { out.setChildRole("select"); getSelectExpression().export(out); } Expression conversion = getConversion(); if (conversion != null) { out.setChildRole("conversion"); conversion.export(out); } out.endElement(); } private String getFlags() { String flags = ""; if (isTunnelParam()) { flags += "t"; } if (isRequiredParam()) { flags += "r"; } if (isImplicitlyRequiredParam()) { flags += "i"; } return flags; } /** * Say that the bound value has the potential to be indexed */ @Override public void setIndexedVariable() {} /** * Ask whether the binding is to be indexed * * @return true if the variable value can be indexed */ @Override public boolean isIndexedVariable() { return false; } /** * Make an elaborator for this expression * * @return an appropriate {@link Elaborator} */ public Elaborator getElaborator() { return new LocalParamElaborator(); } /** * The Elaborator for this kind of expression */ public static class LocalParamElaborator extends PushElaborator { @Override public PushEvaluator elaborateForPush() { LocalParam expr = (LocalParam) getExpression(); SequenceEvaluator selectEvaluator; if (expr.getSelectExpression() == null) { selectEvaluator = null; } else if (expr.referenceCount == FilterExpression.FILTERED) { PullEvaluator pullEval = expr.getSelectExpression().makeElaborator().elaborateForPull(); selectEvaluator = new IndexedVariableEvaluator(pullEval); } else { Expression select = expr.getSelectExpression(); selectEvaluator = new LearningEvaluator( select, select.makeElaborator().lazily(true)); //selectEvaluator = select.makeElaborator().lazily(true); } SequenceEvaluator conversionEval = (expr.getConversion() == null) ? null : expr.getConversion().makeElaborator().eagerly(); return (out, context) -> { int wasSupplied = context.useLocalParameter(expr.variableQName, expr.slotNumber, expr.isTunnelParam()); switch (wasSupplied) { case ParameterSet.SUPPLIED_AND_CHECKED: // No action needed break; case ParameterSet.SUPPLIED: // if a parameter was supplied by the caller, with no type-checking by the caller, // then we may need to convert it to the type required if (conversionEval != null) { context.setLocalVariable(expr.slotNumber, conversionEval.evaluate(context)); // We do an eager evaluation here for safety, because the result of the // type conversion overwrites the slot where the actual supplied parameter // is contained. } break; // don't evaluate the default if a value has been supplied or if it has already been // evaluated by virtue of a forwards reference case ParameterSet.NOT_SUPPLIED: if (expr.isRequiredParam() || expr.isImplicitlyRequiredParam()) { String name = "$" + expr.getVariableQName().getDisplayName(); int suppliedAsTunnel = context.useLocalParameter(expr.variableQName, expr.slotNumber, !expr.isTunnelParam()); String message = "No value supplied for required parameter " + name; if (expr.isImplicitlyRequiredParam()) { message += ". A value is required because " + "the default value is not a valid instance of the required type"; } if (suppliedAsTunnel != ParameterSet.NOT_SUPPLIED) { if (expr.isTunnelParam()) { message += ". A non-tunnel parameter with this name was supplied, but a tunnel parameter is required"; } else { message += ". A tunnel parameter with this name was supplied, but a non-tunnel parameter is required"; } } XPathException e = new XPathException(message); e.setXPathContext(context); e.setErrorCode("XTDE0700"); throw e; } if (selectEvaluator == null) { throw new AssertionError("Internal error: No select expression"); } else { // There is a select attribute: do a lazy evaluation of the expression, // which will already contain any code to force conversion to the required type. int savedOutputState = context.getTemporaryOutputState(); context.setTemporaryOutputState(StandardNames.XSL_WITH_PARAM); Sequence result = selectEvaluator.evaluate(context); context.setLocalVariable(expr.slotNumber, result); context.setTemporaryOutputState(savedOutputState); return null; } } return null; }; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy