All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.flwor.FLWORExpression Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.flwor;

import net.sf.saxon.event.Outputter;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.instruct.TailCall;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.query.QueryModule;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.SequenceType;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
 * This class represents a FLWOR expression, evaluated using tuple streams
 */
public class FLWORExpression extends Expression {

    public List clauses;
    public Operand returnClauseOp;
    public PushEvaluator returnPushEvaluator; // needed if generating push bytecode

    public FLWORExpression() {}

    public void init(List clauses, Expression returnClause) {
        this.clauses = clauses;
        boolean looping = false;
        for (Clause c : clauses) {
            if (isLoopingClause(c)) {
                looping = true;
                break;
            }
        }
        this.returnClauseOp = new Operand(this, returnClause, looping ? REPEATED_RETURN : SINGLE_RETURN);
    }

    /**
     * Get the list of clauses of the FLWOR expression, in the order they are written.
     * This excludes the return clause
     *
     * @return the list of clauses
     */
    /*@NotNull*/
    public List getClauseList() {
        return clauses;
    }

    public static boolean isLoopingClause(Clause c) {
        return c.getClauseKey() == Clause.ClauseName.FOR || c.getClauseKey() == Clause.ClauseName.GROUP_BY
                || c.getClauseKey() == Clause.ClauseName.WINDOW;
    }

    /**
     * Get the return clause of the FLWOR expression
     *
     * @return the expression contained in the return clause
     */

    /*@NotNull*/
    public Expression getReturnClause() {
        return returnClauseOp.getChildExpression();
    }

    /**
     * Get a push-evaluator for the return clause (used from bytecode)
     */

    public PushEvaluator getReturnPushEvaluator() {
        synchronized(this) {
            if (returnPushEvaluator == null) {
                returnPushEvaluator = makeElaborator().elaborateForPush();
            }
        }
        return returnPushEvaluator;
    }

    /**
     * Determine whether a given variable binding belongs to this FLWOR expression
     *
     * @param binding the binding being sought
     * @return true if this binding belongs to one of the clauses of this FLWOR expression
     */

    @Override
    public boolean hasVariableBinding(Binding binding) {
        for (Clause c : clauses) {
            if (clauseHasBinding(c, binding)) {
                return true;
            }
        }
        return false;
    }

    private boolean clauseHasBinding(Clause c, Binding binding) {
        for (Binding b : c.getRangeVariables()) {
            if (b == binding) {
                return true;
            }
        }
        return false;
    }

    /**
     * Ask whether common subexpressions found in the operands of this expression can
     * be extracted and evaluated outside the expression itself. The result is irrelevant
     * in the case of operands evaluated with a different focus, which will never be
     * extracted in this way, even if they have no focus dependency.
     *
     * @return false for this kind of expression
     */
    @Override
    public boolean allowExtractingCommonSubexpressions() {
        return false;
    }

    /**
     * Simplify an expression. This performs any static optimization (by rewriting the expression
     * as a different expression). The default implementation does nothing.
     *
     *
     *
     * @throws net.sf.saxon.trans.XPathException
     *          if an error is discovered during expression
     *          rewriting
     */
    /*@NotNull*/
    @Override
    public Expression simplify() throws XPathException {
        OperandProcessor simplifier =
                op -> op.setChildExpression(op.getChildExpression().simplify());
        for (Clause c : clauses) {
            c.processOperands(simplifier);
        }
        returnClauseOp.setChildExpression(getReturnClause().simplify());
        return this;
    }

    /**
     * Perform type checking of an expression and its subexpressions. This is the second phase of
     * static optimization.
     * 

This checks statically that the operands of the expression have * the correct type; if necessary it generates code to do run-time type checking or type * conversion. A static type error is reported only if execution cannot possibly succeed, that * is, if a run-time type error is inevitable. The call may return a modified form of the expression.

*

This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable. However, the types of such functions and * variables may not be accurately known if they have not been explicitly declared.

*

If the implementation returns a value other than "this", then it is required to ensure that * the location information in the returned expression have been set up correctly. * It should not rely on the caller to do this, although for historical reasons many callers do so.

* * param visitor an expression visitor * @param contextInfo static information about the dynamic context * @return the original expression, rewritten to perform necessary run-time type checks, * and to perform other type-related optimizations * @throws net.sf.saxon.trans.XPathException * if an error is discovered during this phase * (typically a type error) */ /*@NotNull*/ @Override public Expression typeCheck(final ExpressionVisitor visitor, final ContextItemStaticInfo contextInfo) throws XPathException { OperandProcessor typeChecker = op -> op.typeCheck(visitor, contextInfo); for (int i = 0; i < clauses.size(); i++) { clauses.get(i).processOperands(typeChecker); clauses.get(i).typeCheck(visitor, contextInfo); LocalVariableBinding[] bindings = clauses.get(i).getRangeVariables(); for (Binding b : bindings) { List references = new ArrayList<>(); for (int j = i; j < clauses.size(); j++) { clauses.get(j).gatherVariableReferences(visitor, b, references); } ExpressionTool.gatherVariableReferences(getReturnClause(), b, references); clauses.get(i).refineVariableType(visitor, references, getReturnClause()); } } returnClauseOp.typeCheck(visitor, contextInfo); return this; } /** * Determine whether this expression implements its own method for static type checking * * @return true - this expression has a non-trivial implementation of the staticTypeCheck() * method */ @Override public boolean implementsStaticTypeCheck() { for (Clause c : clauses) { switch (c.getClauseKey()) { case LET: case WHERE: continue; default: return false; } } return true; } /** * Static type checking for let expressions is delegated to the expression itself, * and is performed on the "return" expression, to allow further delegation to the branches * of a conditional * * * @param req the required type * @param backwardsCompatible true if backwards compatibility mode applies * @param roleSupplier the role of the expression in relation to the required type * @param visitor an expression visitor * @return the expression after type checking (perhaps augmented with dynamic type checking code) * @throws XPathException if failures occur, for example if the static type of one branch of the conditional * is incompatible with the required type */ @Override public Expression staticTypeCheck(SequenceType req, boolean backwardsCompatible, Supplier roleSupplier, ExpressionVisitor visitor) throws XPathException { // only called if implementsStaticTypeCheck() returns true TypeChecker tc = visitor.getConfiguration().getTypeChecker(backwardsCompatible); returnClauseOp.setChildExpression( tc.staticTypeCheck(getReturnClause(), req, roleSupplier, visitor)); return this; } /** * Determine the data type of the items returned by the expression. * * @return a value such as Type.STRING, Type.BOOLEAN, Type.NUMBER, * Type.NODE, or Type.ITEM (meaning not known at compile time) */ /*@NotNull*/ @Override public ItemType getItemType() { return getReturnClause().getItemType(); } /** * Compute the static cardinality of this expression * * @return the computed cardinality, as one of the values {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_ONE}, * {@link net.sf.saxon.expr.StaticProperty#EXACTLY_ONE}, {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ONE_OR_MORE}, * {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_MORE} */ @Override protected int computeCardinality() { // Assume that simple cases, like a FLWOR whose clauses are all "let" clauses, will have been converted into something else. return StaticProperty.ALLOWS_ZERO_OR_MORE; } /** * Get the immediate sub-expressions of this expression, with information about the relationship * of each expression to its parent expression. Default implementation * returns a zero-length array, appropriate for an expression that has no * sub-expressions. * * @return an iterator containing the sub-expressions of this expression */ @Override public Iterable operands() { final List list = new ArrayList<>(5); try { for (Clause c : clauses) { //noinspection Convert2MethodRef c.processOperands(op -> list.add(op)); } } catch (XPathException e) { throw new IllegalStateException(e); } list.add(returnClauseOp); return list; } private static final OperandRole SINGLE_RETURN = new OperandRole(0, OperandUsage.TRANSMISSION, SequenceType.ANY_SEQUENCE); private static final OperandRole REPEATED_RETURN = new OperandRole(OperandRole.HIGHER_ORDER, OperandUsage.TRANSMISSION, SequenceType.ANY_SEQUENCE); /** * Check to ensure that this expression does not contain any inappropriate 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 { OperandProcessor processor = op -> { op.getChildExpression().checkForUpdatingSubexpressions(); if (op.getChildExpression().isUpdatingExpression()) { throw new XPathException( "An updating expression cannot be used in a clause of a FLWOR expression", "XUST0001"); } }; for (Clause c : clauses) { c.processOperands(processor); } getReturnClause().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 getReturnClause().isUpdatingExpression(); } /** * 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 | PROCESS_METHOD; } /** * Add a representation of this expression to a PathMap. The PathMap captures a map of the nodes visited * by an expression in a source tree. *

The default implementation of this method assumes that an expression does no navigation other than * the navigation done by evaluating its subexpressions, and that the subexpressions are evaluated in the * same context as the containing expression. The method must be overridden for any expression * where these assumptions do not hold. For example, implementations exist for AxisExpression, ParentExpression, * and RootExpression (because they perform navigation), and for the doc(), document(), and collection() * functions because they create a new navigation root. Implementations also exist for PathExpression and * FilterExpression because they have subexpressions that are evaluated in a different context from the * calling expression.

* * @param pathMap the PathMap to which the expression should be added * @param pathMapNodeSet the PathMapNodeSet to which the paths embodied in this expression should be added * @return the pathMapNodeSet representing the points in the source document that are both reachable by this * expression, and that represent possible results of this expression. For an expression that does * navigation, it represents the end of the arc in the path map that describes the navigation route. For other * expressions, it is the same as the input pathMapNode. */ @Override public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, /*@Nullable*/ PathMap.PathMapNodeSet pathMapNodeSet) { for (Clause c : clauses) { c.addToPathMap(pathMap, pathMapNodeSet); } return getReturnClause().addToPathMap(pathMap, pathMapNodeSet); } /** * Inject tracing (or other monitoring) code for each clause * @param injector the code injector responsible for processing each clause of the FLWOR expression */ public void injectCode(CodeInjector injector) { if (injector != null) { for (Clause clause : clauses) { // if there are already trace clauses present, do nothing if (clause instanceof TraceClause) { return; } } List expandedList = new ArrayList<>(clauses.size() * 2); expandedList.add(clauses.get(0)); for (int i = 1; i < clauses.size(); i++) { Clause extraClause = injector.injectClause(this, clauses.get(i - 1)); if (extraClause != null) { expandedList.add(extraClause); } expandedList.add(clauses.get(i)); } Clause extra = injector.injectClause(this, clauses.get(clauses.size() - 1)); if (extra != null) { expandedList.add(extra); } clauses = expandedList; returnClauseOp.setChildExpression(ExpressionTool.injectCode(returnClauseOp.getChildExpression(), injector)); } } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. * * @param out the expression presenter used to display the structure */ @Override public void export(ExpressionPresenter out) throws XPathException { out.startElement("FLWOR", this); for (Clause c : clauses) { c.explain(out); } out.startSubsidiaryElement("return"); getReturnClause().export(out); out.endSubsidiaryElement(); out.endElement(); } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression * @param rebindings variable bindings to be replaced */ /*@NotNull*/ @Override public Expression copy(RebindingMap rebindings) { //verifyParentPointers(); List newClauses = new ArrayList<>(); FLWORExpression f2 = new FLWORExpression(); for (Clause c : clauses) { Clause c2 = c.copy(f2, rebindings); c2.setLocation(c.getLocation()); c2.setRepeated(c.isRepeated()); LocalVariableBinding[] oldBindings = c.getRangeVariables(); LocalVariableBinding[] newBindings = c2.getRangeVariables(); assert oldBindings.length == newBindings.length; for (int i=0; i op.optimize(visitor, contextItemType)); c.optimize(visitor, contextItemType); } // Optimize the return expression returnClauseOp.setChildExpression( getReturnClause().optimize(visitor, contextItemType)); // For a very simple "for" or "let" expression, convert it to a ForExpression or LetExpression now if (clauses.size() == 1) { Clause c = clauses.get(0); if (c instanceof LetClause || (c instanceof ForClause && !(c instanceof ForMemberClause) && ((ForClause)c).getPositionVariable() == null)) { return rewriteForOrLet(visitor, contextItemType); } } // If any 'let' clause declares a variable that is used only once, then inline it. If the variable // is not used at all, then eliminate it boolean tryAgain; boolean changed = false; do { tryAgain = false; for (Clause c : clauses) { if (c.getClauseKey() == Clause.ClauseName.LET) { LetClause lc = (LetClause) c; if (!ExpressionTool.dependsOnVariable(this, new Binding[]{lc.getRangeVariable()})) { clauses.remove(c); opt.trace("Removed unused variable " + lc.getRangeVariable().getVariableQName().getDisplayName(), this); tryAgain = true; break; } boolean suppressInlining = false; for (Clause c2 : clauses) { if (c2.containsNonInlineableVariableReference(lc.getRangeVariable())) { suppressInlining = true; break; } } if (!suppressInlining) { boolean oneRef = lc.getRangeVariable().getNominalReferenceCount() == 1; boolean simpleSeq = lc.getSequence() instanceof VariableReference || lc.getSequence() instanceof Literal; if (oneRef || simpleSeq) { ExpressionTool.replaceVariableReferences(this, lc.getRangeVariable(), lc.getSequence(), true); clauses.remove(c); opt.trace("Inlined variable " + lc.getRangeVariable().getVariableQName().getDisplayName(), this); if (clauses.isEmpty()) { return getReturnClause(); } tryAgain = true; break; } } } } changed |= tryAgain; } while (tryAgain); // If changed, remove any redundant trace clauses if (changed) { for (int i = clauses.size() - 1; i >= 1; i--) { if (clauses.get(i).getClauseKey() == Clause.ClauseName.TRACE && clauses.get(i - 1).getClauseKey() == Clause.ClauseName.TRACE) { clauses.remove(i); } } } // If any 'where' clause depends on the context item, remove this dependency, because it makes // it easier to rearrange where clauses as predicates boolean depends = false; for (Clause w : clauses) { if (w instanceof WhereClause && ExpressionTool.dependsOnFocus(((WhereClause) w).getPredicate())) { depends = true; break; } } if (depends && contextItemType != null) { Expression expr1 = ExpressionTool.tryToFactorOutDot(this, contextItemType.getItemType()); if (expr1 == null || expr1 == this) { //no optimisation possible return this; } resetLocalStaticProperties(); return expr1.optimize(visitor, contextItemType); } // Now convert any terms within WHERE clauses where possible into predicates on the appropriate // expression bound to a variable on a for clause. This enables the resulting filter expression // to be handled using indexing (in Saxon-EE), and it also reduces the number of items that need // to be tested against the predicate Expression expr2 = rewriteWhereClause(visitor, contextItemType); if (expr2 != null && expr2 != this) { return expr2.optimize(visitor, contextItemType); } // If the FLWOR expression consists entirely of FOR and LET clauses, convert it to a ForExpression // or LetExpression. This is largely to take advantage of existing optimizations implemented for those // expressions. boolean allForOrLetExpr = true; for (Clause c : clauses) { if (c instanceof ForClause && !(c instanceof ForMemberClause)) { if (((ForClause)c).getPositionVariable() != null) { allForOrLetExpr = false; break; } } else if (!(c instanceof LetClause)) { allForOrLetExpr = false; break; } } if (allForOrLetExpr) { return rewriteForOrLet(visitor, contextItemType); } return this; } private static class WhereClauseStruct { int whereIndex = 0; WhereClause whereClause; } /** * @param visitor the expression visitor * @param contextItemType the type of the context item * @return We return this expression, with WhereClauses moved up as far as possible in the list of clauses. * A Where clause cannot move above a Count clause because it changes the number of tuples in the tuple stream. * Alternatively, return null if no rewriting is possible. * @throws XPathException if the rewrite fails for any reason */ /*@Nullable*/ private Expression rewriteWhereClause(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException { WhereClause whereClause; int whereIndex = 0; List whereList = new ArrayList<>(); for (Clause c : clauses) { if (c instanceof WhereClause) { WhereClauseStruct wStruct = new WhereClauseStruct(); wStruct.whereClause = (WhereClause) c; //keep track of whereclause from the end of the list of clauses. //We are always attempting to rewrite whereclauses from left to right, // therefore index will always be in snyc wStruct.whereIndex = clauses.size() - whereIndex; whereList.add(wStruct); } whereIndex++; } if (whereList.size() == 0) { return null; } while (!whereList.isEmpty()) { whereClause = whereList.get(0).whereClause; whereIndex = whereList.get(0).whereIndex; Expression condition = whereClause.getPredicate(); List list = new ArrayList<>(5); BooleanExpression.listAndComponents(condition, list); for (int i = list.size() - 1; i >= 0; i--) { Expression term = list.get(i); for (int c = clauses.size() - whereIndex - 1; c >= 0; c--) { Clause clause = clauses.get(c); Binding[] bindingList = clause.getRangeVariables(); // Find the first clause prior to the where clause that declares variables on which the // term of the where clause depends if (ExpressionTool.dependsOnVariable(term, bindingList) || clause.getClauseKey() == Clause.ClauseName.COUNT) { // remove this term from the where clause Expression removedExpr = list.remove(i); if (list.isEmpty()) { // the where clause has no terms left, so remove the clause clauses.remove(clauses.size() - whereIndex); } else { // change the predicate of the where clause to use only those terms that remain whereClause.setPredicate(makeAndCondition(list)); } if ((clause instanceof ForClause) && !((ForClause) clause).isAllowingEmpty()) { // if the clause is a "for" clause, try to add the term as a predicate boolean added = ((ForClause) clause).addPredicate(this, visitor, contextItemType, term); //If we cannot add the WhereClause term as a predicate then put it back into the list of clauses if (!added) { WhereClause newWhere = new WhereClause(this, removedExpr); newWhere.setLocation(clause.getLocation()); clauses.add(c + 1, newWhere); } } else { // the clause is not a "for" clause, so just move the "where" to this place in the list of clauses WhereClause newWhere = new WhereClause(this, term); newWhere.setLocation(clause.getLocation()); clauses.add(c + 1, newWhere); } // we found a variable on which the term depends so we can't move it any further break; } } if (list.size() - 1 == i) { list.remove(i); if (list.isEmpty()) { clauses.remove(clauses.size() - whereIndex); } else { whereClause.setPredicate(makeAndCondition(list)); } WhereClause newWhere = new WhereClause(this, term); newWhere.setLocation(condition.getLocation()); clauses.add(0, newWhere); } } whereList.remove(0); } return this; } /** * Recursive method to make a list of expressions into a AndExpression * * @param list of Expression * @return And Expression of list of expressions */ private Expression makeAndCondition(List list) { if (list.size() == 1) { return list.get(0); } else { return new AndExpression(list.get(0), makeAndCondition(list.subList(1, list.size()))); } } /** * Rewrite a FLWOR expression that consists entirely of "for" and "let" clauses as * a LetExpression or ForExpression * * @param visitor - ExpressionVisitor * @param contextItemType - ExpressionVisitor.ContextItemTyp * @return the rewritten expression * @throws net.sf.saxon.trans.XPathException * if an error occurs */ /*@NotNull*/ private Expression rewriteForOrLet(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException { Expression action = getReturnClause(); CodeInjector injector = null; if (visitor.getStaticContext() instanceof QueryModule) { injector = ((QueryModule) visitor.getStaticContext()).getCodeInjector(); } for (int i = clauses.size() - 1; i >= 0; i--) { if (clauses.get(i) instanceof ForClause) { ForClause forClause = (ForClause) clauses.get(i); ForExpression forExpr; if (forClause.isAllowingEmpty()) { forExpr = new OuterForExpression(); } else { forExpr = new ForExpression(); } forExpr.setLocation(forClause.getLocation()); forExpr.setRetainedStaticContext(getRetainedStaticContext()); //forExpr.setParentExpression(getParentExpression()); forExpr.setAction(action); forExpr.setSequence(forClause.getSequence()); forExpr.setVariableQName(forClause.getRangeVariable().getVariableQName()); forExpr.setRequiredType(forClause.getRangeVariable().getRequiredType()); ExpressionTool.rebindVariableReferences(action, forClause.getRangeVariable(), forExpr); action = forExpr; // if (injector != null) { // action = injector.inject(action, visitor.getStaticContext(), LocationKind.FOR_EXPRESSION, forExpr.getVariableQName()); // } } else { LetClause letClause = (LetClause) clauses.get(i); LetExpression letExpr = new LetExpression(); letExpr.setLocation(letClause.getLocation()); letExpr.setRetainedStaticContext(getRetainedStaticContext()); //letExpr.setParentExpression(getParentExpression()); letExpr.setAction(action); letExpr.setSequence(letClause.getSequence()); letExpr.setVariableQName(letClause.getRangeVariable().getVariableQName()); letExpr.setRequiredType(letClause.getRangeVariable().getRequiredType()); if (letClause.getRangeVariable().isIndexedVariable()) { letExpr.setIndexedVariable(); } //letExpr.setRefCount(letClause.getRangeVariable().getNominalReferenceCount()); ExpressionTool.rebindVariableReferences(action, letClause.getRangeVariable(), letExpr); action = letExpr; // if (injector != null) { // action = injector.inject(action, visitor.getStaticContext(), LocationKind.LET_EXPRESSION, letExpr.getVariableQName()); // } } } action = action.typeCheck(visitor, contextItemType); action = action.optimize(visitor, contextItemType); return action; } /** * Return an Iterator to iterate over the values of a sequence. The value of every * expression can be regarded as a sequence, so this method is supported for all * expressions. This default implementation handles iteration for expressions that * return singleton values: for non-singleton expressions, the subclass must * provide its own implementation. * * @param context supplies the context for evaluation * @return a SequenceIterator that can be used to iterate over the result * of the expression * @throws net.sf.saxon.trans.XPathException * if any dynamic error occurs evaluating the * expression */ /*@NotNull*/ @Override public SequenceIterator iterate(XPathContext context) throws XPathException { return makeElaborator().elaborateForPull().iterate(context); } /** * Process the instruction, without returning any tail calls * * * @param output the destination for the result * @param context The dynamic context, giving access to the current node, * the current variables, etc. * @throws XPathException if a dynamic error occurs */ @Override public void process(Outputter output, XPathContext context) throws XPathException { TailCall tc = makeElaborator().elaborateForPush().processLeavingTail(output, context); Expression.dispatchTailCall(tc); } /** * 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 "FLWOR"; } /** * Produce a short string identifying the expression for use in error messages * * @return a short string, sufficient to identify the expression */ @Override public String toShortString() { StringBuilder sb = new StringBuilder(64); sb.append(clauses.get(0).toShortString()); sb.append(" ... return "); sb.append(getReturnClause().toShortString()); return sb.toString(); } /** * Display the expression as a string */ public String toString() { StringBuilder sb = new StringBuilder(64); for (Clause c : clauses) { sb.append(c.toString()); sb.append(' '); } sb.append(" return "); sb.append(getReturnClause().toString()); return sb.toString(); } /** * Determine whether a variable reference found within a clause of a FLWOR expression is a looping * reference, that is, whether the variable is used more than once * * @param binding the variable binding, which may be bound in a clause of the same FLWOR expression, * or in some containing expression * @return true if a reference to the variable occurs within a loop relative to the binding, that is, if the * variable's value is used more than once. Note that this method only detects a loop that is due to the clauses * of this FLWOR expression itself. A loop in an inner expression or outer expression of the FLWOR expression must * be detected by the caller. */ public boolean hasLoopingVariableReference(final Binding binding) { // Determine the clause that binds the variable (if any) int bindingClause = -1; for (int i = 0; i < clauses.size(); i++) { if (clauseHasBinding(clauses.get(i), binding)) { bindingClause = i; break; } } boolean boundOutside = bindingClause < 0; if (boundOutside) { bindingClause = 0; } // Determine the last clause that contains a reference to the variable. // (If any reference to the variable is a looping reference, then the last one will be) int lastReferencingClause = clauses.size(); // indicates the return clause if (!ExpressionTool.dependsOnVariable(getReturnClause(), new Binding[]{binding})) { // artifice to get a response value from the generic processExpression() method final List response = new ArrayList<>(); OperandProcessor checker = op -> { if (response.isEmpty() && ExpressionTool.dependsOnVariable(op.getChildExpression(), new Binding[]{binding})) { response.add(true); } }; for (int i = clauses.size() - 1; i >= 0; i--) { try { clauses.get(i).processOperands(checker); if (!response.isEmpty()) { lastReferencingClause = i; break; } } catch (XPathException e) { assert false; } } } // If any clause between the binding clause and the last referencing clause is a looping clause, // then the variable is used within a loop for (int i = lastReferencingClause - 1; i >= bindingClause; i--) { if (isLoopingClause(clauses.get(i))) { return true; } } // otherwise there is no loop caused by the clauses of the FLWOR expression itself. return false; } /** * Make an elaborator for this expression * * @return an appropriate {@link Elaborator} */ @Override public Elaborator getElaborator() { return new FLWORElaborator(); } private static class FLWORElaborator extends PullElaborator { @Override public PullEvaluator elaborateForPull() { FLWORExpression expr = (FLWORExpression)getExpression(); PullEvaluator returnPull = expr.getReturnClause().makeElaborator().elaborateForPull(); return context -> { TuplePull stream = new SingularityPull(); for (Clause c : expr.clauses) { stream = c.getPullStream(stream, context); } return new ReturnClauseIterator(stream, returnPull, context); }; } @Override public PushEvaluator elaborateForPush() { FLWORExpression expr = (FLWORExpression) getExpression(); expr.returnPushEvaluator = expr.getReturnClause().makeElaborator().elaborateForPush(); return (output, context) -> { TuplePush destination = new ReturnClausePush(output, expr.returnPushEvaluator); for (int i = expr.clauses.size() - 1; i >= 0; i--) { Clause c = expr.clauses.get(i); destination = c.getPushStream(destination, output, context); } destination.processTuple(context); destination.close(); return null; }; } @Override public UpdateEvaluator elaborateForUpdate() { FLWORExpression expr = (FLWORExpression) getExpression(); UpdateEvaluator returnAction = expr.getReturnClause().makeElaborator().elaborateForUpdate(); return (context, pul) -> { TuplePull stream = new SingularityPull(); for (Clause c : expr.clauses) { stream = c.getPullStream(stream, context); } while (stream.nextTuple(context)) { returnAction.registerUpdates(context, pul); } }; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy