net.sf.saxon.expr.QuantifiedExpression 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;
import net.sf.saxon.expr.elab.BooleanEvaluator;
import net.sf.saxon.expr.elab.PullEvaluator;
import net.sf.saxon.expr.elab.BooleanElaborator;
import net.sf.saxon.expr.elab.Elaborator;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.BooleanFn;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.Item;
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.BooleanValue;
import net.sf.saxon.value.SequenceType;
import java.util.function.Supplier;
/**
* A QuantifiedExpression tests whether some/all items in a sequence satisfy
* some condition.
*/
public class QuantifiedExpression extends Assignation {
private int operator; // Token.SOME or Token.EVERY
/**
* 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 Token.tokens[operator];
}
/**
* Set the operator, either {@link Token#SOME} or {@link Token#EVERY}
*
* @param operator the operator
*/
public void setOperator(int operator) {
this.operator = operator;
}
/**
* Get the operator, either {@link Token#SOME} or {@link Token#EVERY}
*
* @return the operator
*/
public int getOperator() {
return operator;
}
/**
* Determine the static cardinality
*/
@Override
protected int computeCardinality() {
return StaticProperty.EXACTLY_ONE;
}
/**
* Type-check the expression
*/
/*@NotNull*/
@Override
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
// The order of events is critical here. First we ensure that the type of the
// sequence expression is established. This is used to establish the type of the variable,
// which in turn is required when type-checking the action part.
getSequenceOp().typeCheck(visitor, contextInfo);
if (Literal.isEmptySequence(getSequence())) {
return Literal.makeLiteral(BooleanValue.get(operator != Token.SOME), this);
}
// "some" and "every" have no ordering constraints
setSequence(getSequence().unordered(false, false));
SequenceType decl = getRequiredType();
if (decl.getCardinality() == StaticProperty.ALLOWS_ZERO) {
XPathException errNotEmpty = new XPathException("Range variable will never satisfy the type empty-sequence()", "XPTY0004");
errNotEmpty.setIsTypeError(true);
errNotEmpty.setLocation(getLocation());
throw errNotEmpty;
}
SequenceType sequenceType = SequenceType.makeSequenceType(decl.getPrimaryType(),
StaticProperty.ALLOWS_ZERO_OR_MORE);
Supplier role =
() -> new RoleDiagnostic(RoleDiagnostic.VARIABLE, getVariableQName().getDisplayName(), 0);
setSequence(TypeChecker.strictTypeCheck(
getSequence(), sequenceType, role, visitor.getStaticContext()));
ItemType actualItemType = getSequence().getItemType();
refineTypeInformation(actualItemType,
StaticProperty.EXACTLY_ONE,
null,
getSequence().getSpecialProperties(), this);
//declaration = null; // let the garbage collector take it
getActionOp().typeCheck(visitor, contextInfo);
XPathException err = TypeChecker.ebvError(getAction(), visitor.getConfiguration().getTypeHierarchy());
if (err != null) {
err.setLocation(getLocation());
throw err;
}
return this;
}
/**
* 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 contextItemType 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 contextItemType) throws XPathException {
getSequenceOp().optimize(visitor, contextItemType);
getActionOp().optimize(visitor, contextItemType);
Expression ebv = BooleanFn.rewriteEffectiveBooleanValue(getAction(), visitor, contextItemType);
if (ebv != null) {
setAction(ebv);
adoptChildExpression(ebv);
}
if (Literal.hasEffectiveBooleanValue(ebv, true)) {
// some $x satisfies true() => exists($x)
// every $x satisfies true() => true()
if (getOperator() == Token.SOME) {
return SystemFunction.makeCall("exists", getRetainedStaticContext(), getSequence());
} else {
Expression e2 = new Literal(BooleanValue.TRUE);
ExpressionTool.copyLocationInfo(this, e2);
return e2;
}
} else if (Literal.hasEffectiveBooleanValue(ebv, false)) {
// some $x satisfies false() => false()
// every $x satisfies false() => empty($x)
if (getOperator() == Token.SOME) {
Expression e2 = new Literal(BooleanValue.FALSE);
ExpressionTool.copyLocationInfo(this, e2);
return e2;
} else {
return SystemFunction.makeCall("empty", getRetainedStaticContext(), getSequence());
}
}
if (getSequence() instanceof Literal) {
GroundedValue seq = ((Literal)getSequence()).getGroundedValue();
int len = seq.getLength();
if (len == 0) {
Expression e2 = new Literal(BooleanValue.get(getOperator() == Token.EVERY));
ExpressionTool.copyLocationInfo(this, e2);
return e2;
} else if (len == 1) {
if (getAction() instanceof VariableReference && ((VariableReference) getAction()).getBinding() == this) {
return SystemFunction.makeCall("boolean", getRetainedStaticContext(), getSequence());
} else {
replaceVariable(getSequence());
return getAction();
}
}
}
// if streaming, convert to an expression that can be streamed
if (visitor.isOptimizeForStreaming()) {
Expression e3 = visitor.obtainOptimizer().optimizeQuantifiedExpressionForStreaming(this);
if (e3 != null && e3 != this) {
return e3.optimize(visitor, contextItemType);
}
}
return this;
}
/**
* 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 EVALUATE_METHOD;
}
/**
* Check to ensure that this expression does not contain any updating subexpressions.
* This check is overridden for those expressions that permit updating subexpressions.
*
* @throws net.sf.saxon.trans.XPathException
* if the expression has a non-permitted updateing subexpression
*/
@Override
public void checkForUpdatingSubexpressions() throws XPathException {
getSequence().checkForUpdatingSubexpressions();
getAction().checkForUpdatingSubexpressions();
}
/**
* 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 false;
}
/**
* 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) {
QuantifiedExpression qe = new QuantifiedExpression();
ExpressionTool.copyLocationInfo(this, qe);
qe.setOperator(operator);
qe.setVariableQName(variableName);
qe.setRequiredType(requiredType);
qe.setSequence(getSequence().copy(rebindings));
rebindings.put(this, qe);
Expression newAction = getAction().copy(rebindings);
qe.setAction(newAction);
qe.variableName = variableName;
qe.slotNumber = slotNumber;
return qe;
}
/**
* 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;
}
/**
* Evaluate the expression to return a singleton value
*/
@Override
public BooleanValue evaluateItem(XPathContext context) throws XPathException {
return BooleanValue.get(effectiveBooleanValue(context));
}
/**
* Get the result as a boolean
*/
@Override
public boolean effectiveBooleanValue(XPathContext context) throws XPathException {
// First create an iteration of the base sequence.
SequenceIterator base = getSequence().iterate(context);
// Now test to see if some or all of the tests are true. The same
// logic is used for the SOME and EVERY operators
final boolean some = operator == Token.SOME;
int slot = getLocalSlotNumber();
Item it;
while ((it = base.next()) != null) {
context.setLocalVariable(slot, it);
if (some == getAction().effectiveBooleanValue(context)) {
base.close();
return some;
}
}
return !some;
}
/**
* Determine the data type of the items returned by the expression
*
* @return Type.BOOLEAN
*/
/*@NotNull*/
@Override
public ItemType getItemType() {
return BuiltInAtomicType.BOOLEAN;
}
/**
* 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
* @param contextItemType the static type of the context item
*/
@Override
public UType getStaticUType(UType contextItemType) {
return UType.BOOLEAN;
}
/**
* The toString() method for an expression attempts to give a representation of the expression
* in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath.
* In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax
*
* @return a representation of the expression as a string
*/
public String toString() {
return (operator == Token.SOME ? "some" : "every") + " $" + getVariableEQName() +
" in " + getSequence() + " satisfies " +
ExpressionTool.parenthesize(getAction());
}
@Override
public String toShortString() {
return (operator == Token.SOME ? "some" : "every") + " $" + getVariableName() +
" in " + getSequence().toShortString() + " satisfies ...";
}
/**
* 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(Token.tokens[operator], this);
out.emitAttribute("var", getVariableQName());
out.emitAttribute("slot", ""+slotNumber);
getSequence().export(out);
getAction().export(out);
out.endElement();
}
/**
* Make an elaborator for this expression
*
* @return a suitable elaborator
*/
@Override
public Elaborator getElaborator() {
return new QuantifiedExprElaborator();
}
/**
* Elaborator for a quantified expression ({@code some|every X in Y satisfies Z})
*/
public static class QuantifiedExprElaborator extends BooleanElaborator {
public BooleanEvaluator elaborateForBoolean() {
final QuantifiedExpression expr = (QuantifiedExpression) getExpression();
final PullEvaluator selectEval = expr.getSequence().makeElaborator().elaborateForPull();
final BooleanEvaluator satisfiesEval = expr.getAction().makeElaborator().elaborateForBoolean();
final boolean some = expr.getOperator() == Token.SOME;
final int slot = expr.getLocalSlotNumber();
return context -> {
SequenceIterator base = selectEval.iterate(context);
for (Item it; (it = base.next()) != null;) {
context.setLocalVariable(slot, it);
if (some == satisfiesEval.eval(context)) {
base.close();
return some;
}
}
return !some;
};
}
}
}