net.sf.saxon.expr.instruct.GlobalVariable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.PreparedStylesheet;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.om.*;
import net.sf.saxon.query.XQueryFunction;
import net.sf.saxon.query.XQueryFunctionLibrary;
import net.sf.saxon.s9api.HostLanguage;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trace.TraceableComponent;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.Visibility;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.ManualIterator;
import net.sf.saxon.tree.util.IndexedStack;
import net.sf.saxon.type.Affinity;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.IntegerValue;
import net.sf.saxon.value.SequenceType;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
* A compiled global variable in a stylesheet or query.
*/
public class GlobalVariable extends Actor
implements Binding, net.sf.saxon.query.Declaration, TraceableComponent, ContextOriginator {
protected List references = new ArrayList<>(10);
// Note that variableReferences on this list might be dormant;
// that is, they might be disconnected from the live expression tree.
// References are maintained in XQuery but not in XSLT (where they are handled at the level of the
// XSLSourceBinding object)
private StructuredQName variableQName;
private SequenceType requiredType;
private boolean _indexed;
private boolean _isPrivate = false;
private boolean _isAssignable = false;
private GlobalVariable originalVariable;
private int binderySlotNumber;
private boolean _isRequiredParam;
private boolean _isStatic;
/**
* Create a global variable
*/
public GlobalVariable() {
}
/**
* Initialize the properties of the variable
*
* @param select the expression to which the variable is bound
* @param qName the name of the variable
*/
public void init(Expression select, StructuredQName qName) {
variableQName = qName;
setBody(select);
}
/**
* Get the symbolic name of the component
*
* @return the symbolic name
*/
@Override
public SymbolicName getSymbolicName() {
return new SymbolicName(StandardNames.XSL_VARIABLE, variableQName);
}
@Override
public String getTracingTag() {
return "xsl:variable";
}
/**
* 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) {
consumer.accept("name", getVariableQName());
}
/**
* Say whether this variable is declared to be static
* @param declaredStatic true if the variable is declared with static="yes"
*/
public void setStatic(boolean declaredStatic) {
_isStatic = declaredStatic;
}
/**
* Ask whether this variable is declared to be static
* @return true if the variable is declared with static="yes"
*/
public boolean isStatic() {
return this._isStatic;
}
/**
* 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;
}
/**
* Get the Configuration to which this Container belongs
*
* @return the Configuration
*/
private Configuration getConfiguration() {
return getPackageData().getConfiguration();
}
/**
* Say that this (XQuery) variable is a copy of some originally declared variable. It's a
* separate variable when imported into another module, but it retains the link
* to the original.
*
* @param var the variable in the imported module from which this variable is derived
*/
public void setOriginalVariable(GlobalVariable var) {
originalVariable = var;
}
/**
* Get the original declaration of this variable
*
* @return the variable in the imported module from which this variable is derived
*/
public GlobalVariable getOriginalVariable() {
return originalVariable;
}
/**
* Get the original declaration of this variable, or its original declaration, transitively
*
* @return the real variable declaration in some transitively imported module from which this variable is
* ultimately derived
*/
public GlobalVariable getUltimateOriginalVariable() {
if (originalVariable == null) {
return this;
} else {
return originalVariable.getUltimateOriginalVariable();
}
}
/**
* Say whether this variable is unused. Normally, unused variables are not
* compiled. However, in XSLT with debugging enabled (that is, with compileWithTracing
* set), dummy unused variables are created in respect of global variables that are
* declared but never referenced. These variables are included in the list
* of variables known to the Executable, but they are never evaluated, and do
* not have slot numbers allocated in the bindery.
* @param unused set to true if this global variable is to be marked as unused.
*/
public void setUnused(boolean unused) {
this.binderySlotNumber = -9234;
}
/**
* Ask whether this variable is unused. Normally, unused variables are not
* compiled. However, in XSLT with debugging enabled (that is, with compileWithTracing
* set), dummy unused variables are created in respect of global variables that are
* declared but never referenced. These variables are included in the list
* of variables known to the Executable, but they are never evaluated, and do
* not have slot numbers allocated in the bindery.
* @return true if this global variable is marked as unused.
*/
public boolean isUnused() {
return this.binderySlotNumber == -9234;
}
/**
* Ask whether this global variable is private
*
* @return true if this variable is private
*/
public boolean isPrivate() {
return _isPrivate;
}
/**
* Say whether this global variable is private
*
* @param b true if this variable is external
*/
public void setPrivate(boolean b) {
_isPrivate = b;
}
/**
* Indicate whether this variable is assignable using saxon:assign
*
* @param assignable true if this variable is assignable
*/
public void setAssignable(boolean assignable) {
_isAssignable = assignable;
}
/**
* Test whether it is permitted to assign to the variable using the saxon:assign
* extension element. This will only be true if the extra attribute saxon:assignable="yes"
* is present.
*/
@Override
public final boolean isAssignable() {
return _isAssignable;
}
/**
* Get a name identifying the object of the expression, for example a function name, template name,
* variable name, key name, element name, etc. This is used only where the name is known statically.
*
* @return the QName of the object declared or manipulated by this instruction or expression
*/
@Override
public StructuredQName getObjectName() {
return getVariableQName();
}
/**
* Get the value of a particular property of the instruction. Properties
* of XSLT instructions are generally known by the name of the stylesheet attribute
* that defines them.
*
* @param name The name of the required property
* @return The value of the requested property, or null if the property is not available
*/
@Override
public Object getProperty(String name) {
return null;
}
/**
* Get the host language (XSLT, XQuery, XPath) used to implement the code in this container
*
* @return typically {@link HostLanguage#XSLT} or {@link HostLanguage#XQUERY}
*/
public HostLanguage getHostLanguage() {
return getPackageData().getHostLanguage();
}
/**
* Mark this as an indexed variable, to allow fast searching
*/
public void setIndexedVariable() {
_indexed = true;
}
/**
* Ask whether this is an indexed variable
*
* @return true if this variable is indexed
*/
public boolean isIndexedVariable() {
return _indexed;
}
/**
* The expression that initializes a global variable may itself use local variables.
* In this case a stack frame needs to be allocated while evaluating the global variable
*
* @param map The stack frame map for local variables used while evaluating this global
* variable.
*/
public void setContainsLocals(SlotManager map) {
setStackFrameMap(map);
}
/**
* Is this a global variable?
*
* @return true (yes, it is a global variable)
*/
@Override
public boolean isGlobal() {
return true;
}
/**
* Register a variable reference that refers to this global variable
*
* @param ref the variable reference
*/
public void registerReference(BindingReference ref) {
references.add(ref);
}
/**
* Iterate over the references to this variable
*
* @return an iterator over the references: returns objects of class {@link VariableReference}
*/
public Iterator iterateReferences() {
return references.iterator();
}
/**
* Get the slot number allocated to this variable in the Bindery
*
* @return the slot number, that is the position allocated to the variable within the Bindery
*/
public int getBinderySlotNumber() {
return binderySlotNumber;
}
/**
* Set the slot number of this variable in the Bindery
*
* @param s the slot number, that is, the position allocated to this variable within the Bindery
*/
public void setBinderySlotNumber(int s) {
if (!isUnused()) {
binderySlotNumber = s;
}
}
/**
* Indicate that this variable represents a required parameter
*
* @param requiredParam true if this is a required parameter
*/
public void setRequiredParam(boolean requiredParam) {
this._isRequiredParam = requiredParam;
}
/**
* Ask whether this variable represents a required parameter
*
* @return true if this variable represents a required parameter
*/
public boolean isRequiredParam() {
return this._isRequiredParam;
}
/**
* Create a compiled representation of this global variable. Used in XQuery only.
*
* @param exec the executable
* @param slot the slot number allocated to this variable
* @throws XPathException if compile-time errors are found.
*/
public void compile(Executable exec, int slot) throws XPathException {
TypeHierarchy th = getConfiguration().getTypeHierarchy();
setBinderySlotNumber(slot);
if (this instanceof GlobalParam) {
setRequiredParam(getBody() == null);
}
SequenceType type = getRequiredType();
for (BindingReference ref : references) {
ref.fixup(this);
GroundedValue constantValue = null;
int properties = 0;
Expression select = getBody();
if (select instanceof Literal && !(this instanceof GlobalParam)) {
// we can't rely on the constant value because it hasn't yet been type-checked,
// which could change it (eg by numeric promotion). Rather than attempt all the type-checking
// now, we do a quick check. See test bug64
Affinity relation = th.relationship(select.getItemType(), type.getPrimaryType());
if (relation == Affinity.SAME_TYPE || relation == Affinity.SUBSUMED_BY) {
constantValue = ((Literal) select).getGroundedValue();
type = SequenceType.makeSequenceType(SequenceTool.getItemType(constantValue, th), SequenceTool.getCardinality(constantValue));
}
}
if (select != null) {
properties = select.getSpecialProperties();
}
properties |= StaticProperty.NO_NODES_NEWLY_CREATED;
// a variable reference is non-creative even if its initializer is creative
ref.setStaticType(type, constantValue, properties);
}
//exec.registerGlobalVariable(this);
if (isRequiredParam()) {
exec.registerGlobalParameter((GlobalParam) this);
}
}
/**
* Type check the compiled representation of this global variable
*
* @param visitor an expression visitor
* @throws XPathException if compile-time errors are found.
*/
public void typeCheck(/*@NotNull*/ ExpressionVisitor visitor) throws XPathException {
Expression value = getBody();
if (value != null) {
value.checkForUpdatingSubexpressions();
if (value.isUpdatingExpression()) {
throw new XPathException(
"Initializing expression for global variable must not be an updating expression", "XUST0001");
}
Supplier role = () -> new RoleDiagnostic(
RoleDiagnostic.VARIABLE, getVariableQName().getDisplayName(), 0);
ContextItemStaticInfo cit = getConfiguration().makeContextItemStaticInfo(AnyItemType.getInstance(), true);
Expression value2 = TypeChecker.strictTypeCheck(
value.simplify().typeCheck(visitor, cit),
getRequiredType(), role, visitor.getStaticContext());
value2 = value2.optimize(visitor, cit);
setBody(value2);
// the value expression may declare local variables
SlotManager map = getConfiguration().makeSlotManager();
int slots = ExpressionTool.allocateSlots(value2, 0, map);
if (slots > 0) {
setContainsLocals(map);
}
if (getRequiredType() == SequenceType.ANY_SEQUENCE && !(this instanceof GlobalParam)) {
// no type was declared; try to deduce a type from the value
try {
final ItemType itemType = value.getItemType();
final int cardinality = value.getCardinality();
setRequiredType(SequenceType.makeSequenceType(itemType, cardinality));
GroundedValue constantValue = null;
if (value2 instanceof Literal) {
constantValue = ((Literal) value2).getGroundedValue();
}
for (BindingReference reference : references) {
if (reference instanceof VariableReference) {
((VariableReference) reference).refineVariableType(
itemType, cardinality, constantValue, value.getSpecialProperties());
}
}
} catch (Exception err) {
// exceptions can happen because references to variables and functions are still unbound
}
}
}
}
/**
* Check for cycles in this variable definition
*
* @param referees the calls leading up to this one; it's an error if this variable is on the
* stack, because that means it calls itself directly or indirectly. The stack may contain
* variable definitions (GlobalVariable objects) and user-defined functions (UserFunction objects).
* It will never contain the same object more than once.
* @param globalFunctionLibrary the library containing all global functions
* @throws net.sf.saxon.trans.XPathException
* if cycles are found
*/
public void lookForCycles(IndexedStack