package client.net.sf.saxon.ce.expr;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.Controller;
import client.net.sf.saxon.ce.event.PipelineConfiguration;
import client.net.sf.saxon.ce.event.SequenceOutputter;
import client.net.sf.saxon.ce.expr.instruct.SlotManager;
import client.net.sf.saxon.ce.functions.Current;
import client.net.sf.saxon.ce.lib.NamespaceConstant;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.util.SourceLocator;
import client.net.sf.saxon.ce.value.*;
import client.net.sf.saxon.ce.value.StringValue;
import java.util.Iterator;
import java.util.List;
/**
* 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 MAKE_INDEXED_VARIABLE = 12;
public static final int MAKE_SINGLETON_CLOSURE = 13;
public static final int EVALUATE_SUPPLIED_PARAMETER = 14;
private ExpressionTool() {}
/**
* Parse an XPath 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 container
* @param start position of the first significant character in the expression
* @param terminator The token that marks the end of this expression; typically
* Token.EOF, but may for example be a right curly brace
* @param locator the source location of the expression for use in diagnostics
*/
public static Expression make(String expression, StaticContext env,
Container container, int start, int terminator, SourceLocator locator
) throws XPathException {
ExpressionParser parser = new ExpressionParser();
parser.setDefaultContainer(container);
if (terminator == -1) {
terminator = Token.EOF;
}
Expression exp = parser.parse(expression, start, terminator, env);
exp = ExpressionVisitor.make(env, exp.getExecutable()).simplify(exp);
exp.setSourceLocator(locator);
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) {
if(from.sourceLocator != null){
to.setSourceLocator(from.getSourceLocator());
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 retainAllNodes true if there is a need to retain exactly those nodes returned by exp
* even if there are duplicates; false if the caller doesn't mind whether duplicate nodes
* are retained or eliminated
* @return the expression after rewriting
*/
public static Expression unsorted(Optimizer opt, Expression exp, boolean retainAllNodes)
throws XPathException {
if (exp instanceof Literal) {
return exp; // fast exit
}
PromotionOffer offer = new PromotionOffer(opt);
offer.action = PromotionOffer.UNORDERED;
offer.retainAllNodes = retainAllNodes;
return exp.promote(offer, null);
}
/**
* 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 instanceof SuppliedParameterReference) {
return EVALUATE_SUPPLIED_PARAMETER;
} 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 (!Cardinality.allowsMany(exp.getCardinality())) {
// singleton expressions are always evaluated eagerly
return eagerEvaluationMode(exp);
} 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 EVALUATE_SUPPLIED_PARAMETER:
return ((SuppliedParameterReference)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:
return SequenceExtent.makeSequenceExtent(exp.iterate(context));
case PROCESS:
Controller controller = context.getController();
XPathContext c2 = context.newMinorContext();
SequenceOutputter seq = controller.allocateSequenceOutputter(20);
PipelineConfiguration pipe = controller.makePipelineConfiguration();
seq.setPipelineConfiguration(pipe);
c2.setTemporaryReceiver(seq);
seq.open();
exp.process(c2);
seq.close();
ValueRepresentation val = seq.getSequence();
seq.reset();
return val;
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);
}
/**
* 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);
}
/**
* Allocate slot numbers to range variables
* @param exp the expression whose range variables need to have slot numbers assigned
* @param nextFree the next slot number that is available for allocation
* @param frame a SlotManager object that is used to track the mapping of slot numbers
* to variable names for debugging purposes. May be null.
* @return the next unallocated slot number.
*/
public static int allocateSlots(Expression exp, int nextFree, SlotManager frame) {
if (exp instanceof Assignation) {
((Assignation)exp).setSlotNumber(nextFree);
int count = ((Assignation)exp).getRequiredSlots();
nextFree += count;
if (frame != null) {
frame.allocateSlotNumber(((Assignation)exp).getVariableQName());
}
}
if (exp instanceof VariableReference) {
VariableReference var = (VariableReference)exp;
Binding binding = var.getBinding();
if (exp instanceof LocalVariableReference) {
((LocalVariableReference)var).setSlotNumber(binding.getLocalSlotNumber());
}
if (binding instanceof Assignation && binding.getLocalSlotNumber() < 0) {
// This indicates something badly wrong: we've found a variable reference on the tree, that's
// bound to a variable declaration that is no longer on the tree. All we can do is print diagnostics.
// The most common reason for this failure is that the declaration of the variable was removed
// from the tree in the mistaken belief that there were no references to the variable. Variable
// references are counted during the typeCheck phase, so this can happen if typeCheck() fails to
// visit some branch of the expression tree.
Assignation decl = (Assignation)binding;
String msg = "*** Internal Saxon error: local variable encountered whose binding has been deleted";
System.err.println(msg);
System.err.println("Variable name: " + decl.getVariableName());
System.err.println(decl.toString());
throw new IllegalStateException(msg);
}
}
for (Iterator children = exp.iterateSubExpressions(); children.hasNext();) {
Expression child = (Expression)children.next();
nextFree = allocateSlots(child, nextFree, frame);
}
return nextFree;
// Note, we allocate a distinct slot to each range variable, even if the
// scopes don't overlap. This isn't strictly necessary, but might help
// debugging.
}
/**
* Determine the effective boolean value of a sequence, given an iterator over the sequence
* @param iterator An iterator over the sequence whose effective boolean value is required
* @return the effective boolean value
* @throws XPathException if a dynamic error occurs
*/
public static boolean effectiveBooleanValue(SequenceIterator iterator) throws XPathException {
Item first = iterator.next();
if (first == null) {
return false;
}
if (first instanceof NodeInfo) {
return true;
} else {
//first = ((AtomicValue)first).getPrimitiveValue();
if (first instanceof BooleanValue) {
if (iterator.next() != null) {
ebvError("a sequence of two or more items starting with a boolean");
}
return ((BooleanValue)first).getBooleanValue();
} else if (first instanceof StringValue) { // includes anyURI value
if (iterator.next() != null) {
ebvError("a sequence of two or more items starting with a string");
}
return (!((StringValue)first).isZeroLength());
} else if (first instanceof NumericValue) {
if (iterator.next() != null) {
ebvError("a sequence of two or more items starting with a numeric value");
}
final NumericValue n = (NumericValue)first;
return (n.compareTo(0) != 0) && !n.isNaN();
} else {
ebvError("a sequence starting with an atomic value other than a boolean, number, string, or URI");
return false;
}
}
}
/**
* Determine the effective boolean value of a single item
* @param item the item whose effective boolean value is required
* @return the effective boolean value
* @throws XPathException if a dynamic error occurs
*/
public static boolean effectiveBooleanValue(Item item) throws XPathException {
if (item == null) {
return false;
}
if (item instanceof NodeInfo) {
return true;
} else {
if (item instanceof BooleanValue) {
return ((BooleanValue)item).getBooleanValue();
} else if (item instanceof StringValue) { // includes anyURI value
return !((StringValue)item).isZeroLength();
} else if (item instanceof NumericValue) {
final NumericValue n = (NumericValue)item;
return (n.compareTo(0) != 0) && !n.isNaN();
} else {
ebvError("an item other than a boolean, number, string, or URI");
return false;
}
}
}
/**
* Report an error in computing the effective boolean value of an expression
* @param reason the nature of the error
* @throws XPathException
*/
public static void ebvError(String reason) throws XPathException {
XPathException err = new XPathException("Effective boolean value is not defined for " + reason);
err.setErrorCode("FORG0006");
err.setIsTypeError(true);
throw err;
}
/**
* Ask whether an expression has a dependency on the focus
* @param exp the expression
* @return true if the value of the expression depends on the context item, position, or size
*/
public static boolean dependsOnFocus(Expression exp) {
return ((exp.getDependencies() & StaticProperty.DEPENDS_ON_FOCUS) != 0);
}
/**
* Determine whether an expression depends on any one of a set of variables
* @param e the expression being tested
* @param bindingList the set of variables being tested
* @return true if the expression depends on one of the given variables
*/
public static boolean dependsOnVariable(Expression e, Binding[] bindingList) {
if (bindingList == null || bindingList.length == 0) {
return false;
}
if (e instanceof VariableReference) {
for (int i=0; i<bindingList.length; i++) {
if (((VariableReference)e).getBinding() == bindingList[i]) {
return true;
}
}
return false;
// } else if ((e.getDependencies() & StaticProperty.DEPENDS_ON_LOCAL_VARIABLES) == 0) {
// return false;
} else {
for (Iterator children = e.iterateSubExpressions(); children.hasNext();) {
Expression child = (Expression)children.next();
if (dependsOnVariable(child, bindingList)) {
return true;
}
}
return false;
}
}
/**
* Determine whether an expression contains a call on the function with a given fingerprint
* @param exp The expression being tested
* @param qName The name of the function
* @return true if the expression contains a call on the function
*/
public static boolean callsFunction(Expression exp, StructuredQName qName) {
if (exp instanceof FunctionCall && (((FunctionCall)exp).getFunctionName().equals(qName))) {
return true;
}
Iterator iter = exp.iterateSubExpressions();
while (iter.hasNext()) {
Expression e = (Expression)iter.next();
if (callsFunction(e, qName)) {
return true;
}
}
return false;
}
/**
* Resolve calls to the XSLT current() function within an expression
* @param exp the expression within which calls to current() should be resolved
* @param config the Saxon configuration
* @return the expression after resolving calls to current()
*/
public static Expression resolveCallsToCurrentFunction(Expression exp, Configuration config)
throws XPathException {
if (callsFunction(exp, Current.FN_CURRENT)) {
LetExpression let = new LetExpression();
let.setVariableQName(
new StructuredQName("saxon", NamespaceConstant.SAXON, "current" + exp.hashCode()));
let.setRequiredType(SequenceType.SINGLE_ITEM);
let.setSequence(new CurrentItemExpression());
PromotionOffer offer = new PromotionOffer(config.getOptimizer());
offer.action = PromotionOffer.REPLACE_CURRENT;
offer.containingExpression = let;
exp = exp.promote(offer, null);
let.setAction(exp);
return let;
} else {
return exp;
}
}
/**
* Get a list of all references to a particular variable within a subtree
* @param exp the expression at the root of the subtree
* @param binding the variable binding whose references are sought
* @param list a list to be populated with the references to this variable
*/
public static void gatherVariableReferences(Expression exp, Binding binding, List list) {
if (exp instanceof VariableReference &&
((VariableReference)exp).getBinding() == binding) {
list.add(exp);
} else {
for (Iterator iter = exp.iterateSubExpressions(); iter.hasNext(); ) {
gatherVariableReferences((Expression)iter.next(), binding, list);
}
}
}
/**
* Determine how often a variable is referenced. This is the number of times
* it is referenced at run-time: so a reference in a loop counts as "many". This code
* currently handles local variables (Let expressions) and function parameters. It is
* not currently used for XSLT template parameters. It's not the end of the world if
* the answer is wrong (unless it's wrongly given as zero), but if wrongly returned as
* 1 then the variable will be repeatedly evaluated.
* @param exp the expression within which variable references are to be counted
* @param binding identifies the variable of interest
* @param inLoop true if the expression is within a loop, in which case a reference counts as many.
* This should be set to false on the initial call, it may be set to true on an internal recursive
* call
* @return the number of references. The interesting values are 0, 1, "many" (represented
* by any value >1), 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) {
rcount += (inLoop ? 10 : 1);
} else if ((exp.getDependencies() & StaticProperty.DEPENDS_ON_LOCAL_VARIABLES) == 0) {
return 0;
} else {
for (Iterator iter = exp.iterateSubExpressions(); iter.hasNext(); ) {
Expression child = (Expression)iter.next();
boolean childLoop = inLoop || (exp.hasLoopingSubexpression(child));
rcount += getReferenceCount(child, binding, childLoop);
}
}
return rcount;
}
/**
* 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);
}
}
}
}
// 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.