net.sf.saxon.expr.instruct.Block 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.instruct;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.ma.zeno.ZenoSequence;
import net.sf.saxon.om.AxisInfo;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.iter.ListIterator;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
/**
* An expression that delivers the concatenation of the results of its subexpressions. This may
* represent an XSLT sequence constructor, or an XPath/XQuery expression of the form (a,b,c,d).
*/
public class Block extends Instruction {
// TODO: allow the last expression in a Block to be a tail-call of a function, at least in push mode
private final Operand[] operanda;
private boolean allNodesUntyped;
/**
* Create a block, supplying its child expressions
* @param children the child expressions in order
*/
public Block(Expression[] children) {
operanda = new Operand[children.length];
for (int i=0; i operands() {
return Arrays.asList(operanda);
}
/**
* Ask whether this expression is, or contains, the binding of a given variable
*
* @param binding the variable binding
* @return true if this expression is the variable binding (for example a ForExpression
* or LetExpression) or if it is a FLWOR expression that binds the variable in one of its
* clauses.
*/
@Override
public boolean hasVariableBinding(Binding binding) {
if (binding instanceof LocalParam) {
for (Operand o : operanda) {
if (o.getChildExpression() == binding) {
return true;
}
}
}
return false;
}
/**
* Static factory method to create a block. If one of the arguments is already a block,
* the contents will be merged into a new composite block
*
* @param e1 the first subexpression (child) of the block
* @param e2 the second subexpression (child) of the block
* @return a Block containing the two subexpressions, and if either of them is a block, it will
* have been collapsed to create a flattened sequence
*/
public static Expression makeBlock(/*@Nullable*/ Expression e1, Expression e2) {
if (e1 == null || Literal.isEmptySequence(e1)) {
return e2;
}
if (e2 == null || Literal.isEmptySequence(e2)) {
return e1;
}
if (e1 instanceof Block || e2 instanceof Block) {
List list = new ArrayList<>(10);
if (e1 instanceof Block) {
for (Operand o : e1.operands()) {
list.add(o.getChildExpression());
}
} else {
list.add(e1);
}
if (e2 instanceof Block) {
for (Operand o : e2.operands()) {
list.add(o.getChildExpression());
}
} else {
list.add(e2);
}
Expression[] exps = new Expression[list.size()];
exps = list.toArray(exps);
return new Block(exps);
} else {
Expression[] exps = {e1, e2};
return new Block(exps);
}
}
/**
* Static factory method to create a block from a list of expressions
*
* @param list the list of expressions making up this block. The members of the List must
* be instances of Expression. The list is effectively copied; subsequent changes
* to the contents of the list have no effect.
* @return a Block containing the two subexpressions, and if either of them is a block, it will
* have been collapsed to create a flattened sequence
*/
public static Expression makeBlock(List list) {
if (list.isEmpty()) {
return Literal.makeEmptySequence();
} else if (list.size() == 1) {
return list.get(0);
} else {
Expression[] exps = new Expression[list.size()];
exps = list.toArray(exps);
return new Block(exps);
}
}
@Override
public String getExpressionName() {
return "sequence";
}
/**
* Get the children of this instruction
*
* @return the children of this instruction, as an array of Operand objects. May return
* a zero-length array if there are no children.
*/
public Operand[] getOperanda() {
return operanda;
}
@Override
protected int computeSpecialProperties() {
if (size() == 0) {
// An empty sequence has all special properties except "has side effects".
return StaticProperty.SPECIAL_PROPERTY_MASK & ~StaticProperty.HAS_SIDE_EFFECTS;
}
int p = super.computeSpecialProperties();
if (allNodesUntyped) {
p |= StaticProperty.ALL_NODES_UNTYPED;
}
// if all the expressions are axis expressions, we have a same-document node-set
boolean allAxisExpressions = true;
boolean allChildAxis = true;
boolean allSubtreeAxis = true;
for (Operand o : operands()) {
Expression childExpr = o.getChildExpression();
if (!(childExpr instanceof AxisExpression)) {
allAxisExpressions = false;
allChildAxis = false;
allSubtreeAxis = false;
break;
}
int axis = ((AxisExpression) childExpr).getAxis();
if (axis != AxisInfo.CHILD) {
allChildAxis = false;
}
if (!AxisInfo.isSubtreeAxis[axis]) {
allSubtreeAxis = false;
}
}
if (allAxisExpressions) {
p |= StaticProperty.CONTEXT_DOCUMENT_NODESET |
StaticProperty.SINGLE_DOCUMENT_NODESET |
StaticProperty.NO_NODES_NEWLY_CREATED;
// if they all use the child axis, then we have a peer node-set
if (allChildAxis) {
p |= StaticProperty.PEER_NODESET;
}
if (allSubtreeAxis) {
p |= StaticProperty.SUBTREE_NODESET;
}
// special case: selecting attributes then children, node-set is sorted
if (size() == 2 &&
((AxisExpression) child(0)).getAxis() == AxisInfo.ATTRIBUTE &&
((AxisExpression) child(1)).getAxis() == AxisInfo.CHILD) {
p |= StaticProperty.ORDERED_NODESET;
}
}
return p;
}
@Override
public boolean implementsStaticTypeCheck() {
return true;
}
/**
* Static type checking for let expressions is delegated to the child expressions,
* 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 {
TypeChecker tc = visitor.getConfiguration().getTypeChecker(backwardsCompatible);
if (backwardsCompatible && !Cardinality.allowsMany(req.getCardinality())) {
Expression first = FirstItemExpression.makeFirstItemExpression(this);
return tc.staticTypeCheck(first, req, roleSupplier, visitor);
}
Expression[] checked = new Expression[operanda.length];
SequenceType subReq = req;
if (req.getCardinality() != StaticProperty.ALLOWS_ZERO_OR_MORE) {
subReq = SequenceType.makeSequenceType(req.getPrimaryType(), StaticProperty.ALLOWS_ZERO_OR_MORE);
}
for (int i=0; i 0 && isLiteralText[i] && isLiteralText[i - 1]) {
hasAdjacentTextNodes = true;
}
}
if (hasAdjacentTextNodes) {
List content = new ArrayList<>(size());
String pendingText = null;
for (int i = 0; i < size(); i++) {
if (isLiteralText[i]) {
pendingText = (pendingText == null ? "" : pendingText) +
((StringLiteral) ((ValueOf) child(i)).getSelect()).getString();
} else {
if (pendingText != null) {
ValueOf inst = new ValueOf(new StringLiteral(pendingText), false, false);
content.add(inst);
pendingText = null;
}
content.add(child(i));
}
}
if (pendingText != null) {
ValueOf inst = new ValueOf(new StringLiteral(pendingText), false, false);
content.add(inst);
}
return makeBlock(content);
} else {
return this;
}
}
/**
* 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) {
Expression[] c2 = new Expression[size()];
for (int c = 0; c < size(); c++) {
c2[c] = child(c).copy(rebindings);
}
Block b2 = new Block(c2);
for (int c = 0; c < size(); c++) {
b2.adoptChildExpression(c2[c]);
}
b2.allNodesUntyped = allNodesUntyped;
ExpressionTool.copyLocationInfo(this, b2);
return b2;
}
/**
* Determine the data type of the items returned by this expression
*
* @return the data type
*/
/*@NotNull*/
@Override
public final ItemType getItemType() {
if (size() == 0) {
return ErrorType.getInstance();
}
ItemType t1 = null;
TypeHierarchy th = getConfiguration().getTypeHierarchy();
for (int i = 0; i < size(); i++) {
Expression childExpr = child(i);
if (!(childExpr instanceof MessageInstr)) {
ItemType t = childExpr.getItemType();
t1 = t1 == null ? t : Type.getCommonSuperType(t1, t, th);
if (t1 instanceof AnyItemType) {
return t1; // no point going any further
}
}
}
return t1 == null ? ErrorType.getInstance() : t1;
}
/**
* 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) {
if (isInstruction()) {
return super.getStaticUType(contextItemType);
} else {
if (size() == 0) {
return UType.VOID;
}
UType t1 = child(0).getStaticUType(contextItemType);
for (int i = 1; i < size(); i++) {
t1 = t1.union(child(i).getStaticUType(contextItemType));
if (t1 == UType.ANY) {
return t1; // no point going any further
}
}
return t1;
}
}
/**
* Determine the cardinality of the expression
*/
@Override
protected int computeCardinality() {
if (size() == 0) {
return StaticProperty.EMPTY;
}
int c1 = child(0).getCardinality();
for (int i = 1; i < size(); i++) {
c1 = Cardinality.sum(c1, child(i).getCardinality());
if (c1 == StaticProperty.ALLOWS_MANY) {
break;
}
}
return c1;
}
/**
* Determine whether this instruction creates new nodes.
* This implementation returns true if any child instruction
* returns true.
*/
@Override
public final boolean mayCreateNewNodes() {
return someOperandCreatesNewNodes();
}
/**
* 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 {
if (size() < 2) {
return;
}
boolean updating = false;
boolean nonUpdating = false;
for (Operand o : operands()) {
Expression child = o.getChildExpression();
if (ExpressionTool.isNotAllowedInUpdatingContext(child)) {
if (updating) {
XPathException err = new XPathException(
"If any subexpression is updating, then all must be updating", "XUST0001");
err.setLocation(child.getLocation());
throw err;
}
nonUpdating = true;
}
if (child.isUpdatingExpression()) {
if (nonUpdating) {
XPathException err = new XPathException(
"If any subexpression is updating, then all must be updating", "XUST0001");
err.setLocation(child.getLocation());
throw err;
}
updating = true;
}
}
}
/**
* Determine whether this is a vacuous expression as defined in the XQuery update specification
*
* @return true if this expression is vacuous
*/
@Override
public boolean isVacuousExpression() {
// true if all subexpressions are vacuous
for (Operand o : operands()) {
Expression child = o.getChildExpression();
if (!child.isVacuousExpression()) {
return false;
}
}
return true;
}
/**
* Simplify an expression. This performs any static optimization (by rewriting the expression
* as a different expression). The default implementation does nothing.
*
*
*
* @throws XPathException if an error is discovered during expression
* rewriting
*/
/*@NotNull*/
@Override
public Expression simplify() throws XPathException {
boolean allAtomic = true;
boolean nested = false;
for (int c = 0; c < size(); c++) {
setChild(c, child(c).simplify());
if (!Literal.isAtomic(child(c))) {
allAtomic = false;
}
if (child(c) instanceof Block) {
nested = true;
} else if (Literal.isEmptySequence(child(c))) {
nested = true;
}
}
if (size() == 1) {
Expression e = getOperanda()[0].getChildExpression();
e.setParentExpression(getParentExpression());
return e;
} else if (size() == 0) {
Expression result = Literal.makeEmptySequence();
ExpressionTool.copyLocationInfo(this, result);
result.setParentExpression(getParentExpression());
return result;
} else if (nested) {
List list = new ArrayList<>(size() * 2);
flatten(list);
Expression[] children = new Expression[list.size()];
for (int i = 0; i < list.size(); i++) {
children[i] = list.get(i);
}
Block newBlock = new Block(children);
ExpressionTool.copyLocationInfo(this, newBlock);
return newBlock.simplify();
} else if (allAtomic) {
AtomicValue[] values = new AtomicValue[size()];
for (int c = 0; c < size(); c++) {
values[c] = (AtomicValue) ((Literal) child(c)).getGroundedValue();
}
@SuppressWarnings("Convert2Diamond")
Expression result = Literal.makeLiteral(new SequenceExtent.Of(values), this);
result.setParentExpression(getParentExpression());
return result;
} else {
return this;
}
}
/**
* Simplify the contents of a Block by merging any nested blocks, merging adjacent
* literals, and eliminating any empty sequences.
*
* @param targetList the new list of expressions comprising the contents of the block
* after simplification
*/
private void flatten(List targetList) {
List- currentLiteralList = null;
for (Operand o : operands()) {
Expression child = o.getChildExpression();
if (Literal.isEmptySequence(child)) {
// do nothing, omit it from the output
} else if (child instanceof Block) {
flushCurrentLiteralList(currentLiteralList, targetList);
currentLiteralList = null;
((Block) child).flatten(targetList);
} else if (child instanceof Literal && !(((Literal) child).getGroundedValue() instanceof IntegerRange)) {
SequenceIterator iterator = ((Literal) child).getGroundedValue().iterate();
if (currentLiteralList == null) {
currentLiteralList = new ArrayList<>(10);
}
for (Item item; (item = iterator.next()) != null; ) {
currentLiteralList.add(item);
}
// no-op
} else {
flushCurrentLiteralList(currentLiteralList, targetList);
currentLiteralList = null;
targetList.add(child);
}
}
flushCurrentLiteralList(currentLiteralList, targetList);
}
private void flushCurrentLiteralList(List
- currentLiteralList, List
list) {
if (currentLiteralList != null) {
ListIterator.Of- iter = new ListIterator.Of<>(currentLiteralList);
Literal lit = Literal.makeLiteral(iter.materialize(), this);
list.add(lit);
}
}
/**
* Determine whether the block is a candidate for evaluation using a "shared append expression"
* where the result of the evaluation is a sequence implemented as a list of subsequences
* @return true if the block is a candidate for "shared append" processing
*/
public boolean isCandidateForSharedAppend() {
for (Operand o : operands()) {
Expression exp = o.getChildExpression();
if (exp instanceof VariableReference || exp instanceof Literal) {
return true;
}
}
return false;
}
/*@NotNull*/
@Override
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
typeCheckChildren(visitor, contextInfo);
if (neverReturnsTypedNodes(this, visitor.getConfiguration().getTypeHierarchy())) {
resetLocalStaticProperties();
allNodesUntyped = true;
}
return this;
}
/*@NotNull*/
@Override
public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
optimizeChildren(visitor, contextInfo);
boolean canSimplify = false;
boolean prevLiteral = false;
// Simplify the expression by collapsing nested blocks and merging adjacent literals
for (Operand o : operands()) {
Expression child = o.getChildExpression();
if (child instanceof Block) {
canSimplify = true;
break;
}
if (child instanceof Literal && !(((Literal) child).getGroundedValue() instanceof IntegerRange)) {
if (prevLiteral || Literal.isEmptySequence(child)) {
canSimplify = true;
break;
}
prevLiteral = true;
} else {
prevLiteral = false;
}
}
if (canSimplify) {
List
list = new ArrayList<>(size() * 2);
flatten(list);
Expression result = Block.makeBlock(list);
result.setRetainedStaticContext(getRetainedStaticContext());
return result;
}
if (size() == 0) {
return Literal.makeEmptySequence();
} else if (size() == 1) {
return child(0);
} else {
return this;
}
}
/**
* Check that any elements and attributes constructed or returned by this expression are acceptable
* in the content model of a given complex type. It's always OK to say yes, since the check will be
* repeated at run-time. The process of checking element and attribute constructors against the content
* model of a complex type also registers the type of content expected of those constructors, so the
* static validation can continue recursively.
*/
@Override
public void checkPermittedContents(SchemaType parentType, boolean whole) throws XPathException {
for (Operand o : operands()) {
Expression child = o.getChildExpression();
child.checkPermittedContents(parentType, false);
}
}
/**
* 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("sequence", this);
for (Operand o : operands()) {
Expression child = o.getChildExpression();
child.export(out);
}
out.endElement();
}
@Override
public String toShortString() {
return "(" + child(0).toShortString() + ", ...)";
}
/**
* 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. This implementation provides both iterate() and
* process() methods natively.
*/
@Override
public int getImplementationMethod() {
return ITERATE_METHOD | PROCESS_METHOD;
}
/**
* Iterate over the results of all the child expressions
*/
/*@NotNull*/
@Override
public SequenceIterator iterate(XPathContext context) throws XPathException {
if (size() == 0) {
return EmptyIterator.getInstance();
} else if (size() == 1) {
return child(0).iterate(context);
} else {
return new BlockIterator(operanda, context);
}
}
@Override
public String getStreamerName() {
return "Block";
}
/**
* Make an elaborator for this expression
*
* @return a suitable elaborator
*/
@Override
public Elaborator getElaborator() {
return new BlockElaborator();
}
@FunctionalInterface
public interface ChainAction {
ZenoSequence perform(ZenoSequence in, XPathContext context) throws XPathException;
}
/**
* Elaborator for a "Block", which is typically either an XPath sequence expression (a, b, c)
* or an XSLT sequence constructor
*/
public static class BlockElaborator extends PullElaborator {
@Override
public SequenceEvaluator lazily(boolean repeatable) {
final Block expr = (Block) getExpression();
if (expr.isCandidateForSharedAppend()) {
return new SharedAppendEvaluator(expr);
} else {
return super.lazily(repeatable);
}
}
public PullEvaluator elaborateForPull() {
final Block expr = (Block) getExpression();
final Operand[] operanda = expr.getOperanda();
final int size = operanda.length;
final PullEvaluator[] actions = new PullEvaluator[size];
for (int i = 0; i < size; i++) {
actions[i] = operanda[i].getChildExpression().makeElaborator().elaborateForPull();
}
return context -> new BlockIterator(actions, context);
}
private static class BlockIterator extends AbstractBlockIterator {
private final PullEvaluator[] pullers;
public BlockIterator(PullEvaluator[] pullers, XPathContext context) {
this.pullers = pullers;
init(pullers.length, context);
}
@Override
public SequenceIterator getNthChildIterator(int n) throws XPathException {
return pullers[n].iterate(context);
}
}
@Override
public PushEvaluator elaborateForPush() {
final Block expr = (Block) getExpression();
final Operand[] operanda = expr.getOperanda();
final int size = operanda.length;
final PushEvaluator[] actions = new PushEvaluator[size];
for (int i = 0; i < size; i++) {
actions[i] = operanda[i].getChildExpression().makeElaborator().elaborateForPush();
}
switch (size) {
// Unroll loop for small sequence constructors
case 2: {
PushEvaluator act0 = actions[0];
PushEvaluator act1 = actions[1];
return (out, context) -> {
TailCall tail = act0.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
return act1.processLeavingTail(out, context);
};
}
case 3: {
PushEvaluator act0 = actions[0];
PushEvaluator act1 = actions[1];
PushEvaluator act2 = actions[2];
return (out, context) -> {
TailCall tail = act0.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
tail = act1.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
return act2.processLeavingTail(out, context);
};
}
case 4: {
PushEvaluator act0 = actions[0];
PushEvaluator act1 = actions[1];
PushEvaluator act2 = actions[2];
PushEvaluator act3 = actions[3];
return (out, context) -> {
TailCall tail = act0.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
tail = act1.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
tail = act2.processLeavingTail(out, context);
Expression.dispatchTailCall(tail);
return act3.processLeavingTail(out, context);
};
}
default:
return (out, context) -> {
TailCall tail = null;
for (int i = 0; i < size; i++) {
while (tail != null) {
tail = tail.processLeavingTail();
}
tail = actions[i].processLeavingTail(out, context);
}
return tail;
};
}
}
@Override
public UpdateEvaluator elaborateForUpdate() {
final Block expr = (Block) getExpression();
final Operand[] operanda = expr.getOperanda();
final int size = operanda.length;
final UpdateEvaluator[] actions = new UpdateEvaluator[size];
for (int i=0; i {
for (UpdateEvaluator action : actions) {
action.registerUpdates(context, pul);
}
};
}
}
}