net.sf.saxon.expr.ExpressionTool Maven / Gradle / Ivy
package net.sf.saxon.expr;
import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.SequenceOutputter;
import net.sf.saxon.functions.Current;
import net.sf.saxon.functions.ExtensionFunctionCall;
import net.sf.saxon.instruct.Block;
import net.sf.saxon.instruct.SlotManager;
import net.sf.saxon.instruct.UserFunction;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.value.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
/**
* This class, ExpressionTool, contains a number of useful static methods
* for manipulating expressions. Most importantly, it provides the factory
* method make() for constructing a new expression
*/
public class ExpressionTool {
public static final int UNDECIDED = -1;
public static final int NO_EVALUATION_NEEDED = 0;
public static final int EVALUATE_VARIABLE = 1;
public static final int MAKE_CLOSURE = 3;
public static final int MAKE_MEMO_CLOSURE = 4;
public static final int RETURN_EMPTY_SEQUENCE = 5;
public static final int EVALUATE_AND_MATERIALIZE_VARIABLE = 6;
public static final int CALL_EVALUATE_ITEM = 7;
public static final int ITERATE_AND_MATERIALIZE = 8;
public static final int PROCESS = 9;
public static final int LAZY_TAIL_EXPRESSION = 10;
public static final int SHARED_APPEND_EXPRESSION = 11;
public static final int MAKE_INDEXED_VARIABLE = 12;
public static final int MAKE_SINGLETON_CLOSURE = 13;
private ExpressionTool() {}
/**
* Parse an expression. This performs the basic analysis of the expression against the
* grammar, it binds variable references and function calls to variable definitions and
* function definitions, and it performs context-independent expression rewriting for
* optimization purposes.
*
* @param expression The expression (as a character string)
* @param env An object giving information about the compile-time
* context of the expression
* @param start position of the first significant character in the expression
* @param terminator The token that marks the end of this expression; typically
* Tokenizer.EOF, but may for example be a right curly brace
* @param lineNumber the line number of the start of the expression
* @param compileWithTracing true if diagnostic tracing during expression parsing is required
* @return an object of type Expression
* @throws XPathException if the expression contains a static error
*/
public static Expression make(String expression, StaticContext env,
int start, int terminator, int lineNumber, boolean compileWithTracing) throws XPathException {
ExpressionParser parser = new ExpressionParser();
parser.setCompileWithTracing(compileWithTracing);
if (terminator == -1) {
terminator = Token.EOF;
}
Expression exp = parser.parse(expression, start, terminator, lineNumber, env);
exp = ExpressionVisitor.make(env).simplify(exp);
return exp;
}
/**
* Copy location information (the line number and reference to the container) from one expression
* to another
* @param from the expression containing the location information
* @param to the expression to which the information is to be copied
*/
public static void copyLocationInfo(Expression from, Expression to) {
if (from != null && to != null) {
to.setLocationId(from.getLocationId());
to.setContainer(from.getContainer());
}
}
/**
* Remove unwanted sorting from an expression, at compile time
* @param opt the expression optimizer
* @param exp the expression to be optimized
* @param eliminateDuplicates true if there is a need to eliminate duplicates even though
* there is no need to deliver results in order.
* @return the expression after rewriting
*/
public static Expression unsorted(Optimizer opt, Expression exp, boolean eliminateDuplicates)
throws XPathException {
if (exp instanceof Literal) {
return exp; // fast exit
}
PromotionOffer offer = new PromotionOffer(opt);
offer.action = PromotionOffer.UNORDERED;
offer.mustEliminateDuplicates = eliminateDuplicates;
return exp.promote(offer);
}
/**
* Remove unwanted sorting from an expression, at compile time, if and only if it is known
* that the result of the expression will be homogeneous (all nodes, or all atomic values).
* This is done when we need the effective boolean value of a sequence: the EBV of a
* homogenous sequence does not depend on its order, but this is not true when atomic
* values and nodes are mixed: (N, AV) is true, but (AV, N) is an error.
* @param opt the expression optimizer
* @param exp the expression to be optimized
* @param eliminateDuplicates true if there is a need to eliminate duplicate nodes even though there
* is no requirement to deliver results in order
* @return the expression after rewriting
*/
public static Expression unsortedIfHomogeneous(Optimizer opt, Expression exp, boolean eliminateDuplicates)
throws XPathException {
if (exp instanceof Literal) {
return exp; // fast exit
}
if (exp.getItemType(opt.getConfiguration().getTypeHierarchy()) instanceof AnyItemType) {
return exp;
} else {
PromotionOffer offer = new PromotionOffer(opt);
offer.action = PromotionOffer.UNORDERED;
offer.mustEliminateDuplicates = eliminateDuplicates;
return exp.promote(offer);
}
}
/**
* Determine the method of evaluation to be used when lazy evaluation of an expression is
* preferred. This method is called at compile time, after all optimizations have been done,
* to determine the preferred strategy for lazy evaluation, depending on the type of expression.
*
* @param exp the expression to be evaluated
* @return an integer constant identifying the evaluation mode
*/
public static int lazyEvaluationMode(Expression exp) {
if (exp instanceof Literal) {
return NO_EVALUATION_NEEDED;
} else if (exp instanceof VariableReference) {
return EVALUATE_VARIABLE;
} else if ((exp.getDependencies() &
( StaticProperty.DEPENDS_ON_POSITION |
StaticProperty.DEPENDS_ON_LAST |
StaticProperty.DEPENDS_ON_CURRENT_ITEM |
StaticProperty.DEPENDS_ON_CURRENT_GROUP |
StaticProperty.DEPENDS_ON_REGEX_GROUP )) != 0) {
// we can't save these values in the closure, so we evaluate
// the expression now if they are needed
return eagerEvaluationMode(exp);
} else if (exp instanceof ErrorExpression) {
return CALL_EVALUATE_ITEM;
// evaluateItem() on an error expression throws the latent exception
} else if (exp instanceof LazyExpression) {
// A LazyExpression is always evaluated lazily (if at all possible) to
// prevent spurious errors (see opt017)
if (Cardinality.allowsMany(exp.getCardinality())) {
return MAKE_MEMO_CLOSURE;
} else {
return MAKE_SINGLETON_CLOSURE;
}
} else if (!Cardinality.allowsMany(exp.getCardinality())) {
// singleton expressions are always evaluated eagerly
return eagerEvaluationMode(exp);
} else if (exp instanceof TailExpression) {
// Treat tail recursion as a special case, to avoid creating a deeply-nested
// tree of Closures. If this expression is a TailExpression, and its first
// argument is also a TailExpression, we combine the two TailExpressions into
// one and return a closure over that.
TailExpression tail = (TailExpression)exp;
Expression base = tail.getBaseExpression();
if (base instanceof VariableReference) {
return LAZY_TAIL_EXPRESSION;
} else {
return MAKE_CLOSURE;
}
} else if (exp instanceof Block &&
((Block)exp).getChildren().length == 2 &&
(((Block)exp).getChildren()[0] instanceof VariableReference ||
((Block)exp).getChildren()[0] instanceof Literal)) {
// If the expression is a Block, that is, it is appending a value to a sequence,
// then we have the opportunity to use a shared list underpinning the old value and
// the new. This takes precedence over lazy evaluation (it would be possible to do this
// lazily, but more difficult). We currently only do this for the common case of a two-argument
// append expression, in the case where the first argument is either a value, or a variable
// reference identifying a value. The most common case is that the first argument is a reference
// to an argument of recursive function, where the recursive function returns the result of
// appending to the sequence.
return SHARED_APPEND_EXPRESSION;
} else {
// create a Closure, a wrapper for the expression and its context
return MAKE_CLOSURE;
}
}
/**
* Determine the method of evaluation to be used when lazy evaluation of an expression is
* preferred. This method is called at compile time, after all optimizations have been done,
* to determine the preferred strategy for lazy evaluation, depending on the type of expression.
*
* @param exp the expression to be evaluated
* @return an integer constant identifying the evaluation mode
*/
public static int eagerEvaluationMode(Expression exp) {
if (exp instanceof Literal && !(((Literal)exp).getValue() instanceof Closure)) {
return NO_EVALUATION_NEEDED;
}
if (exp instanceof VariableReference) {
return EVALUATE_AND_MATERIALIZE_VARIABLE;
}
int m = exp.getImplementationMethod();
if ((m & Expression.EVALUATE_METHOD) != 0) {
return CALL_EVALUATE_ITEM;
} else if ((m & Expression.ITERATE_METHOD) != 0) {
return ITERATE_AND_MATERIALIZE;
} else {
return PROCESS;
}
}
/**
* Do lazy evaluation of an expression. This will return a value, which may optionally
* be a SequenceIntent, which is a wrapper around an iterator over the value of the expression.
* @param exp the expression to be evaluated
* @param evaluationMode the evaluation mode for this expression
* @param context the run-time evaluation context for the expression. If
* the expression is not evaluated immediately, then parts of the
* context on which the expression depends need to be saved as part of
* the Closure
* @param ref an indication of how the value will be used. The value 1 indicates that the value
* is only expected to be used once, so that there is no need to keep it in memory. A small value >1
* indicates multiple references, so the value will be saved when first evaluated. The special value
* FILTERED indicates a reference within a loop of the form $x[predicate], indicating that the value
* should be saved in a way that permits indexing.
* @exception XPathException if any error occurs in evaluating the
* expression
* @return a value: either the actual value obtained by evaluating the
* expression, or a Closure containing all the information needed to
* evaluate it later
*/
public static ValueRepresentation evaluate(Expression exp, int evaluationMode, XPathContext context, int ref)
throws XPathException {
switch (evaluationMode) {
case NO_EVALUATION_NEEDED:
return ((Literal)exp).getValue();
case EVALUATE_VARIABLE:
return ((VariableReference)exp).evaluateVariable(context);
case MAKE_CLOSURE:
return Closure.make(exp, context, ref);
//return new SequenceExtent(exp.iterate(context));
case MAKE_MEMO_CLOSURE:
return Closure.make(exp, context, (ref==1 ? 10 : ref));
case MAKE_SINGLETON_CLOSURE:
return new SingletonClosure(exp, context);
case RETURN_EMPTY_SEQUENCE:
return EmptySequence.getInstance();
case EVALUATE_AND_MATERIALIZE_VARIABLE:
ValueRepresentation v = ((VariableReference)exp).evaluateVariable(context);
if (v instanceof Closure) {
return SequenceExtent.makeSequenceExtent(((Closure)v).iterate());
} else {
return v;
}
case CALL_EVALUATE_ITEM:
Item item = exp.evaluateItem(context);
if (item == null) {
return EmptySequence.getInstance();
} else {
return item;
}
case UNDECIDED:
case ITERATE_AND_MATERIALIZE:
if (ref == FilterExpression.FILTERED) {
return context.getConfiguration().getOptimizer().makeSequenceExtent(exp, ref, context);
} else {
return SequenceExtent.makeSequenceExtent(exp.iterate(context));
}
case PROCESS:
Controller controller = context.getController();
XPathContext c2 = context.newMinorContext();
c2.setOrigin(exp);
SequenceOutputter seq = controller.allocateSequenceOutputter(20);
PipelineConfiguration pipe = controller.makePipelineConfiguration();
pipe.setHostLanguage(exp.getHostLanguage());
seq.setPipelineConfiguration(pipe);
c2.setTemporaryReceiver(seq);
seq.open();
exp.process(c2);
seq.close();
ValueRepresentation val = seq.getSequence();
seq.reset();
return val;
case LAZY_TAIL_EXPRESSION: {
TailExpression tail = (TailExpression)exp;
VariableReference vr = (VariableReference)tail.getBaseExpression();
ValueRepresentation base = evaluate(vr, EVALUATE_VARIABLE, context, ref);
if (base instanceof MemoClosure) {
SequenceIterator it = ((MemoClosure)base).iterate();
base = ((GroundedIterator)it).materialize();
}
if (base instanceof IntegerRange) {
long start = ((IntegerRange)base).getStart() + 1;
long end = ((IntegerRange)base).getEnd();
if (start == end) {
return Int64Value.makeIntegerValue(end);
} else {
return new IntegerRange(start, end);
}
}
if (base instanceof SequenceExtent) {
return new SequenceExtent(
(SequenceExtent)base,
tail.getStart() - 1,
((SequenceExtent)base).getLength() - tail.getStart() + 1);
}
return Closure.make(tail, context, ref);
}
case SHARED_APPEND_EXPRESSION: {
Block block = (Block)exp;
Expression base = block.getChildren()[0];
Value baseVal;
if (base instanceof Literal) {
baseVal = ((Literal)base).getValue();
} else if (base instanceof VariableReference) {
baseVal = Value.asValue(evaluate(base, EVALUATE_VARIABLE, context, ref));
if (baseVal instanceof MemoClosure && ((MemoClosure)baseVal).isFullyRead()) {
baseVal = ((MemoClosure)baseVal).materialize();
}
} else {
throw new AssertionError("base of shared append expression is of class " + base.getClass());
}
if (baseVal instanceof ShareableSequence && ((ShareableSequence)baseVal).isShareable()) {
List list = ((ShareableSequence)baseVal).getList();
SequenceIterator iter = block.getChildren()[1].iterate(context);
while (true) {
Item i = iter.next();
if (i == null) {
break;
}
list.add(i);
}
return new ShareableSequence(list);
} else {
List list = new ArrayList(20);
SequenceIterator iter = baseVal.iterate();
while (true) {
Item i = iter.next();
if (i == null) {
break;
}
list.add(i);
}
iter = block.getChildren()[1].iterate(context);
while (true) {
Item i = iter.next();
if (i == null) {
break;
}
list.add(i);
}
return new ShareableSequence(list);
}
}
case MAKE_INDEXED_VARIABLE:
return context.getConfiguration().getOptimizer().makeIndexedValue(exp.iterate(context));
default:
throw new IllegalArgumentException("Unknown evaluation mode " + evaluationMode);
}
}
/**
* Do lazy evaluation of an expression. This will return a value, which may optionally
* be a SequenceIntent, which is a wrapper around an iterator over the value of the expression.
* @param exp the expression to be evaluated
* @param context the run-time evaluation context for the expression. If
* the expression is not evaluated immediately, then parts of the
* context on which the expression depends need to be saved as part of
* the Closure
* @param ref an indication of how the value will be used. The value 1 indicates that the value
* is only expected to be used once, so that there is no need to keep it in memory. A small value >1
* indicates multiple references, so the value will be saved when first evaluated. The special value
* FILTERED indicates a reference within a loop of the form $x[predicate], indicating that the value
* should be saved in a way that permits indexing.
* @return a value: either the actual value obtained by evaluating the
* expression, or a Closure containing all the information needed to
* evaluate it later
* @throws XPathException if any error occurs in evaluating the
* expression
*/
public static ValueRepresentation lazyEvaluate(Expression exp, XPathContext context, int ref) throws XPathException {
final int evaluationMode = lazyEvaluationMode(exp);
return evaluate(exp, evaluationMode, context, ref);
}
/**
* Evaluate an expression now; lazy evaluation is not permitted in this case
* @param exp the expression to be evaluated
* @param context the run-time evaluation context
* @exception net.sf.saxon.trans.XPathException if any dynamic error occurs evaluating the
* expression
* @return the result of evaluating the expression
*/
public static ValueRepresentation eagerEvaluate(Expression exp, XPathContext context) throws XPathException {
final int evaluationMode = eagerEvaluationMode(exp);
return evaluate(exp, evaluationMode, context, 10);
}
/**
* Scan an expression to find and mark any recursive tail function calls
* @param exp the expression to be analyzed
* @param qName the name of the containing function
* @param arity the arity of the containing function
* @return 0 if no tail call was found; 1 if a tail call to a different function was found;
* 2 if a tail call to the specified function was found. In this case the
* UserFunctionCall object representing the tail function call will also have been marked as
* a tail call.
*/
public static int markTailFunctionCalls(Expression exp, StructuredQName qName, int arity) {
return exp.markTailFunctionCalls(qName, arity);
}
/**
* Construct indent string, for diagnostic output
*
* @param level the indentation level (the number of spaces to return)
* @return a string of "level*2" spaces
*/
public static String indent(int level) {
String s = "";
for (int i=0; i1), and the special value FILTERED, which indicates that there are
* multiple references and one or more of them is of the form $x[....] indicating that an
* index might be useful.
*/
public static int getReferenceCount(Expression exp, Binding binding, boolean inLoop) {
int rcount = 0;
if (exp instanceof VariableReference && ((VariableReference)exp).getBinding() == binding) {
if (((VariableReference)exp).isFiltered()) {
return FilterExpression.FILTERED;
} else {
rcount += (inLoop ? 10 : 1);
}
} else {
for (Iterator iter = exp.iterateSubExpressions(); iter.hasNext(); ) {
Expression child = (Expression)iter.next();
boolean childLoop = inLoop || (exp.hasLoopingSubexpression(child));
rcount += getReferenceCount(child, binding, childLoop);
if (rcount >= FilterExpression.FILTERED) {
break;
}
}
}
return rcount;
}
/**
* Gather the set of all the subexpressions of an expression (the transitive closure)
* @param exp the parent expression
* @param set the set to be populated; on return it will contain all the subexpressions.
* Beware that testing for membership of a set of expressions relies on the equals() comparison,
* which does not test identity.
*/
public static void gatherAllSubExpressions(Expression exp, HashSet set) {
set.add(exp);
for (Iterator iter = exp.iterateSubExpressions(); iter.hasNext(); ) {
gatherAllSubExpressions((Expression)iter.next(), set);
}
}
/**
* Get the size of an expression tree (the number of subexpressions it contains)
* @param exp the expression whose size is required
* @return the size of the expression tree, as the number of nodes
*/
public static int expressionSize(Expression exp) {
int total = 1;
for (Iterator iter = exp.iterateSubExpressions(); iter.hasNext(); ) {
total += expressionSize((Expression)iter.next());
}
return total;
}
/**
* Rebind all variable references to a binding
* @param exp the expression whose contained variable references are to be rebound
* @param oldBinding the old binding for the variable references
* @param newBinding the new binding to which the variables should be rebound
*/
public static void rebindVariableReferences(
Expression exp, Binding oldBinding, Binding newBinding) {
if (exp instanceof VariableReference) {
if (((VariableReference)exp).getBinding() == oldBinding) {
((VariableReference)exp).fixup(newBinding);
}
} else {
Iterator iter = exp.iterateSubExpressions();
while (iter.hasNext()) {
Expression e = (Expression)iter.next();
rebindVariableReferences(e, oldBinding, newBinding);
}
}
}
}
//
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the
// License at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Michael H. Kay.
//
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
//
// Contributor(s): none.
//
© 2015 - 2025 Weber Informatics LLC | Privacy Policy