net.sf.saxon.expr.RangeExpression Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.Elaborator;
import net.sf.saxon.expr.elab.ItemEvaluator;
import net.sf.saxon.expr.elab.PullElaborator;
import net.sf.saxon.expr.elab.PullEvaluator;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.SequenceIterator;
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.UType;
import net.sf.saxon.value.Int64Value;
import net.sf.saxon.value.IntegerRange;
import net.sf.saxon.value.IntegerValue;
import net.sf.saxon.value.SequenceType;
import java.math.BigInteger;
import java.util.function.Supplier;
/**
* A RangeExpression is an expression that represents an integer sequence as
* a pair of end-points (for example "x to y"). This implementation also
* supports the experimental syntax "x by y to z", with y defaulting to 1.
*/
public class RangeExpression extends Expression {
private Operand start;
private Operand step;
private Operand end;
private boolean defaultedStep;
/**
* Construct a stepping range expression of the form S by B. If S is a RangeExpression,
* we collapse this into a 3-operand RangeExpression. If S is anything else, we generate
* a function call saxon:_step-over-sequence(S, B). Experimental feature.
* @param lhs the first operand
* @param rhs the second operand
* @return the constructed expression
* @throws XPathException if not valid
*/
public static RangeExpression makeSteppingExpression(Expression lhs, Expression rhs) throws XPathException {
if (!(lhs instanceof RangeExpression && ((RangeExpression)lhs).defaultedStep)) {
throw new XPathException("Temporary restriction: 'by' operator must be preceded by 'x to y'");
}
return new RangeExpression(((RangeExpression) lhs).getStartExpression(), rhs, ((RangeExpression) lhs).getEndExpression());
}
public boolean isDefaultedStep() {
return defaultedStep;
}
/**
* Construct a RangeExpression with increment of 1
* @param start expression that computes the start of the range
* @param end expression that computes the end of the range
*/
public RangeExpression(Expression start, Expression end) {
this(start, new Literal(Int64Value.PLUS_ONE), end);
defaultedStep = true;
}
/**
* Construct a RangeExpression with specified increment
*
* @param start expression that computes the start of the range
* @param step expression to compute interval between successive values
* @param end expression that computes the end of the range
*/
public RangeExpression(Expression start, Expression step, Expression end) {
this.start = new Operand(this, start, OperandRole.SINGLE_ATOMIC);
this.step = new Operand(this, step, OperandRole.SINGLE_ATOMIC);
this.end = new Operand(this, end, OperandRole.SINGLE_ATOMIC);
adoptChildExpression(start);
adoptChildExpression(step);
adoptChildExpression(end);
}
public Expression getStartExpression() {
return start.getChildExpression();
}
public Expression getStepExpression() {
return step.getChildExpression();
}
public Expression getEndExpression() {
return end.getChildExpression();
}
/**
* Type-check the expression
*/
/*@NotNull*/
@Override
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
start.typeCheck(visitor, contextInfo);
step.typeCheck(visitor, contextInfo);
end.typeCheck(visitor, contextInfo);
boolean backCompat = visitor.getStaticContext().isInBackwardsCompatibleMode();
TypeChecker tc = visitor.getConfiguration().getTypeChecker(backCompat);
Supplier role0 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, "to", 0);
start.setChildExpression(tc.staticTypeCheck(
getStartExpression(), SequenceType.OPTIONAL_INTEGER, role0, visitor));
Supplier role1 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, "by", 1);
step.setChildExpression(tc.staticTypeCheck(
getStepExpression(), SequenceType.OPTIONAL_INTEGER, role1, visitor));
Supplier role2 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, "to", 1);
end.setChildExpression(tc.staticTypeCheck(
getEndExpression(), SequenceType.OPTIONAL_INTEGER, role2, visitor));
return makeConstantRange();
}
/**
* Perform optimisation of an expression and its subexpressions.
* 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 net.sf.saxon.type.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)
*/
/*@NotNull*/
@Override
public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
start.optimize(visitor, contextInfo);
step.optimize(visitor, contextInfo);
end.optimize(visitor, contextInfo);
return makeConstantRange();
}
/**
* 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() {
return operandList(start, step, end);
}
private Expression makeConstantRange() throws XPathException {
if (getStartExpression() instanceof Literal
&& getStepExpression() instanceof Literal
&& getEndExpression() instanceof Literal) {
Expression result;
GroundedValue v0 = ((Literal) getStartExpression()).getGroundedValue();
GroundedValue v1 = ((Literal) getStepExpression()).getGroundedValue();
GroundedValue v2 = ((Literal) getEndExpression()).getGroundedValue();
if (v0.getLength()==0 || v1.getLength()==0 || v2.getLength()==0) {
result = Literal.makeEmptySequence();
} else if (v0 instanceof Int64Value && v1 instanceof Int64Value && v2 instanceof Int64Value) {
long i0 = ((Int64Value) v0).longValue();
long i1 = ((Int64Value) v1).longValue();
long i2 = ((Int64Value) v2).longValue();
if (i1 == 0 || i0 > i2) {
result = Literal.makeEmptySequence();
} else {
if (Math.abs((i2 - i0)/i1) > Integer.MAX_VALUE) {
throw new XPathException("Maximum length of sequence in Saxon is " + Integer.MAX_VALUE, "XPDY0130");
}
result = Literal.makeLiteral(i1 < 0 ? new IntegerRange(i2, i1, i0) : new IntegerRange(i0, i1, i2), this);
}
} else {
BigInteger i0 = ((IntegerValue) v0).asBigInteger();
BigInteger i1 = ((IntegerValue) v1).asBigInteger();
BigInteger i2 = ((IntegerValue) v2).asBigInteger();
if (i0.equals(BigInteger.ZERO) || i0.compareTo(i2) > 0) {
result = Literal.makeEmptySequence();
} else if (i0.equals(i2)) {
result = Literal.makeLiteral(Int64Value.makeIntegerValue(i0), this);
} else {
// if (Math.abs((i2 - i0) / i1) > Integer.MAX_VALUE) {
// throw new XPathException("Maximum length of sequence in Saxon is " + Integer.MAX_VALUE, "XPDY0130");
// }
return this;
}
}
ExpressionTool.copyLocationInfo(this, result);
return result;
}
return this;
}
/**
* Get the data type of the items returned
*/
/*@NotNull*/
@Override
public ItemType getItemType() {
return BuiltInAtomicType.INTEGER;
}
/**
* Get the static type of the expression as a UType, following precisely the type
* inference rules defined in the XSLT 3.0 specification.
*
* @return the static item type of the expression according to the XSLT 3.0 defined rules
*/
@Override
public UType getStaticUType(UType contextItemType) {
return UType.DECIMAL;
}
/**
* Determine the static cardinality
*/
@Override
protected int computeCardinality() {
return StaticProperty.ALLOWS_ZERO_OR_MORE;
}
/**
* 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.
*/
/*@Nullable*/
@Override
public IntegerValue[] getIntegerBounds() {
IntegerValue[] start = getStartExpression().getIntegerBounds();
IntegerValue[] end = getEndExpression().getIntegerBounds();
if (start == null || end == null) {
return null;
} else {
// range is from the smallest possible start value to the largest possible end value
return new IntegerValue[]{start[0], end[1]};
}
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
* @param rebindings variables that need to be re-bound
*/
/*@NotNull*/
@Override
public Expression copy(RebindingMap rebindings) {
RangeExpression exp = new RangeExpression(
getStartExpression().copy(rebindings),
getStepExpression().copy(rebindings),
getEndExpression().copy(rebindings));
ExpressionTool.copyLocationInfo(this, exp);
return exp;
}
/**
* 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 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 export() output displaying the expression.
*/
@Override
public String getExpressionName() {
return "range";
}
/**
* Determine the special properties of this expression
*
* @return {@link StaticProperty#NO_NODES_NEWLY_CREATED}.
*/
@Override
protected int computeSpecialProperties() {
int p = super.computeSpecialProperties();
return p | StaticProperty.NO_NODES_NEWLY_CREATED;
}
/**
* Is this expression the same as another expression?
*/
public boolean equals(Object other) {
if (other instanceof RangeExpression && hasCompatibleStaticContext((Expression) other)) {
RangeExpression b = (RangeExpression) other;
Expression start1 = getStartExpression();
Expression step1 = getStepExpression();
Expression end1 = getEndExpression();
Expression start2 = b.getStartExpression();
Expression step2 = b.getStepExpression();
Expression end2 = b.getEndExpression();
return start1.equals(start2) && step1.equals(step2) && end1.equals(end2);
}
return false;
}
/**
* Compute a hash code, which will then be cached for later use
*
* @return a computed hash code
*/
@Override
protected int computeHashCode() {
return getStartExpression().hashCode() ^ (getStepExpression().hashCode()<<3) ^ (getEndExpression().hashCode()<<7);
}
/**
* The toString() method for an expression attempts to give a representation of the expression
* in an XPath-like form.
* For subclasses of Expression that represent XPath expressions, the result should always be a string that
* parses as an XPath 3.0 expression. The expression produced should be equivalent to the original making certain
* assumptions about the static context. In general the expansion will make no assumptions about namespace bindings,
* except that (a) the prefix "xs" is used to refer to the XML Schema namespace, and (b) the default function namespace
* is assumed to be the "fn" namespace.
* In the case of XSLT instructions and XQuery expressions, the toString() method gives an abstracted view of the syntax
* that is not designed in general to be parseable.
*
* @return a representation of the expression as a string
*/
@Override
public String toString() {
String by = getStepExpression().toString();
return getStartExpression().toString() + " to " + getEndExpression().toString()
+ (by.equals("1") ? "" : (" by " + by));
}
@Override
public String toShortString() {
String by = getStepExpression().toShortString();
return getStartExpression().toShortString() + " to " + getEndExpression().toShortString()
+ (by.equals("1") ? "" : (" by " + by));
}
/**
* Diagnostic print of expression structure. The abstract expression tree
* is written to the supplied output destination.
*
* @param out the output destination for the displayed expression tree
*/
@Override
public void export(ExpressionPresenter out) throws XPathException {
// TODO: export "by" expression
out.startElement("to", this);
getStartExpression().export(out);
getEndExpression().export(out);
out.endElement();
}
/**
* Return an iteration over the sequence
*/
/*@NotNull*/
@Override
public SequenceIterator iterate(XPathContext context) throws XPathException {
IntegerValue av1 = (IntegerValue) getStartExpression().evaluateItem(context);
IntegerValue av2 = (IntegerValue) getStepExpression().evaluateItem(context);
IntegerValue av3 = (IntegerValue) getEndExpression().evaluateItem(context);
return AscendingRangeIterator.makeRangeIterator(av1, av2, av3);
}
@Override
public Elaborator getElaborator() {
return new RangeElaborator();
}
public static class RangeElaborator extends PullElaborator {
@Override
public PullEvaluator elaborateForPull() {
RangeExpression expr = (RangeExpression) getExpression();
ItemEvaluator iv1 = expr.getStartExpression().makeElaborator().elaborateForItem();
ItemEvaluator iv2 = expr.getStepExpression().makeElaborator().elaborateForItem();
ItemEvaluator iv3 = expr.getEndExpression().makeElaborator().elaborateForItem();
return (context) -> {
IntegerValue av1 = (IntegerValue) iv1.eval(context);
IntegerValue av2 = (IntegerValue) iv2.eval(context);
IntegerValue av3 = (IntegerValue) iv3.eval(context);
return AscendingRangeIterator.makeRangeIterator(av1, av2, av3);
};
}
}
}