1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst;
7   
8   import org.antlr.v4.runtime.Token;
9   import org.antlr.v4.runtime.tree.ParseTree;
10  import org.antlr.v4.runtime.tree.TerminalNode;
11  
12  import java.math.BigDecimal;
13  import java.math.BigInteger;
14  import java.util.ArrayList;
15  import java.util.LinkedHashMap;
16  import java.util.List;
17  import java.util.ListIterator;
18  import java.util.Map;
19  import java.util.stream.Collectors;
20  import java.util.stream.Stream;
21  
22  import dev.metaschema.core.metapath.IExpression;
23  import dev.metaschema.core.metapath.InvalidMetapathGrammarException;
24  import dev.metaschema.core.metapath.StaticContext;
25  import dev.metaschema.core.metapath.StaticMetapathException;
26  import dev.metaschema.core.metapath.antlr.Metapath10;
27  import dev.metaschema.core.metapath.antlr.Metapath10Lexer;
28  import dev.metaschema.core.metapath.cst.items.ArraySequenceConstructor;
29  import dev.metaschema.core.metapath.cst.items.ArraySquareConstructor;
30  import dev.metaschema.core.metapath.cst.items.DecimalLiteral;
31  import dev.metaschema.core.metapath.cst.items.EmptySequence;
32  import dev.metaschema.core.metapath.cst.items.Except;
33  import dev.metaschema.core.metapath.cst.items.IntegerLiteral;
34  import dev.metaschema.core.metapath.cst.items.Intersect;
35  import dev.metaschema.core.metapath.cst.items.MapConstructor;
36  import dev.metaschema.core.metapath.cst.items.PostfixLookup;
37  import dev.metaschema.core.metapath.cst.items.Quantified;
38  import dev.metaschema.core.metapath.cst.items.Range;
39  import dev.metaschema.core.metapath.cst.items.SequenceExpression;
40  import dev.metaschema.core.metapath.cst.items.SimpleMap;
41  import dev.metaschema.core.metapath.cst.items.StringConcat;
42  import dev.metaschema.core.metapath.cst.items.StringLiteral;
43  import dev.metaschema.core.metapath.cst.items.UnaryLookup;
44  import dev.metaschema.core.metapath.cst.items.Union;
45  import dev.metaschema.core.metapath.cst.logic.And;
46  import dev.metaschema.core.metapath.cst.logic.GeneralComparison;
47  import dev.metaschema.core.metapath.cst.logic.IBooleanLogicExpression;
48  import dev.metaschema.core.metapath.cst.logic.If;
49  import dev.metaschema.core.metapath.cst.logic.Or;
50  import dev.metaschema.core.metapath.cst.logic.PredicateExpression;
51  import dev.metaschema.core.metapath.cst.logic.ValueComparison;
52  import dev.metaschema.core.metapath.cst.math.Addition;
53  import dev.metaschema.core.metapath.cst.math.Division;
54  import dev.metaschema.core.metapath.cst.math.IntegerDivision;
55  import dev.metaschema.core.metapath.cst.math.Modulo;
56  import dev.metaschema.core.metapath.cst.math.Multiplication;
57  import dev.metaschema.core.metapath.cst.math.Negate;
58  import dev.metaschema.core.metapath.cst.math.Subtraction;
59  import dev.metaschema.core.metapath.cst.path.Axis;
60  import dev.metaschema.core.metapath.cst.path.ContextItem;
61  import dev.metaschema.core.metapath.cst.path.FlagStep;
62  import dev.metaschema.core.metapath.cst.path.INodeTestExpression;
63  import dev.metaschema.core.metapath.cst.path.IWildcardMatcher;
64  import dev.metaschema.core.metapath.cst.path.KindNodeTest;
65  import dev.metaschema.core.metapath.cst.path.ModelInstanceStep;
66  import dev.metaschema.core.metapath.cst.path.NameNodeTest;
67  import dev.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath;
68  import dev.metaschema.core.metapath.cst.path.RelativeSlashPath;
69  import dev.metaschema.core.metapath.cst.path.RootDoubleSlashPath;
70  import dev.metaschema.core.metapath.cst.path.RootSlashOnlyPath;
71  import dev.metaschema.core.metapath.cst.path.RootSlashPath;
72  import dev.metaschema.core.metapath.cst.path.Step;
73  import dev.metaschema.core.metapath.cst.path.WildcardNodeTest;
74  import dev.metaschema.core.metapath.cst.type.Cast;
75  import dev.metaschema.core.metapath.cst.type.Castable;
76  import dev.metaschema.core.metapath.cst.type.InstanceOf;
77  import dev.metaschema.core.metapath.cst.type.Treat;
78  import dev.metaschema.core.metapath.cst.type.TypeTestSupport;
79  import dev.metaschema.core.metapath.function.ComparisonFunctions;
80  import dev.metaschema.core.metapath.function.IArgument;
81  import dev.metaschema.core.metapath.item.atomic.IIntegerItem;
82  import dev.metaschema.core.metapath.item.function.IKeySpecifier;
83  import dev.metaschema.core.metapath.item.function.impl.AbstractKeySpecifier;
84  import dev.metaschema.core.metapath.type.IAtomicOrUnionType;
85  import dev.metaschema.core.metapath.type.IItemType;
86  import dev.metaschema.core.metapath.type.ISequenceType;
87  import dev.metaschema.core.metapath.type.Occurrence;
88  import dev.metaschema.core.qname.IEnhancedQName;
89  import dev.metaschema.core.util.CollectionUtil;
90  import dev.metaschema.core.util.ObjectUtils;
91  import edu.umd.cs.findbugs.annotations.NonNull;
92  
93  /**
94   * Supports converting a Metapath abstract syntax tree (AST) generated by
95   * <a href="https://www.antlr.org/">ANTLRv4</a> into a compact syntax tree
96   * (CST).
97   */
98  @SuppressWarnings({
99      "PMD.GodClass", "PMD.CyclomaticComplexity", // acceptable complexity
100     "PMD.CouplingBetweenObjects" // needed
101 })
102 // TODO: Support node comparisons
103 // https://www.w3.org/TR/xpath-31/#id-node-comparisons
104 public class BuildCSTVisitor
105     extends AbstractCSTVisitorBase {
106   @NonNull
107   private static final ISequenceType DEFAULT_FUNCTION_SEQUENCE_TYPE
108       = ISequenceType.of(IItemType.item(), Occurrence.ZERO_OR_MORE);
109 
110   @NonNull
111   private final StaticContext context;
112 
113   /**
114    * Construct a new compact syntax tree generating visitor.
115    *
116    * @param context
117    *          the static Metapath evaluation context
118    */
119   public BuildCSTVisitor(@NonNull StaticContext context) {
120     this.context = context;
121   }
122 
123   // ============================================================
124   // Expressions - https://www.w3.org/TR/xpath-31/#id-expressions
125   // ============================================================
126 
127   /**
128    * Get the static Metapath evaluation context.
129    *
130    * @return the context
131    */
132   @NonNull
133   protected StaticContext getContext() {
134     return context;
135   }
136 
137   @Override
138   protected IExpression handleExpr(Metapath10.ExprContext ctx) {
139     return handleNAiryCollection(ctx, children -> {
140       assert children != null;
141       return new SequenceExpression(ObjectUtils.notNull(ctx.getText()), children);
142     });
143   }
144 
145   // =================================================================
146   // Literal Expressions - https://www.w3.org/TR/xpath-31/#id-literals
147   // =================================================================
148 
149   @Override
150   protected IExpression handleStringLiteral(Metapath10.LiteralContext ctx) {
151     ParseTree tree = ctx.getChild(0);
152     return new StringLiteral(ObjectUtils.notNull(ctx.getText()), ObjectUtils.notNull(tree.getText()));
153   }
154 
155   @Override
156   protected IExpression handleNumericLiteral(Metapath10.NumericliteralContext ctx) {
157     ParseTree tree = ctx.getChild(0);
158     Token token = (Token) tree.getPayload();
159 
160     String text = ObjectUtils.notNull(ctx.getText());
161 
162     IExpression retval;
163     switch (token.getType()) {
164     case Metapath10Lexer.IntegerLiteral:
165       retval = new IntegerLiteral(text, new BigInteger(token.getText()));
166       break;
167     case Metapath10Lexer.DecimalLiteral:
168     case Metapath10Lexer.DoubleLiteral:
169       retval = new DecimalLiteral(text, new BigDecimal(token.getText()));
170       break;
171     default:
172       throw new UnsupportedOperationException(token.getText());
173     }
174     return retval;
175   }
176 
177   // ==================================================================
178   // Variable References - https://www.w3.org/TR/xpath-31/#id-variables
179   // ==================================================================
180 
181   @Override
182   protected IExpression handleVarref(Metapath10.VarrefContext ctx) {
183     return new VariableReference(
184         ObjectUtils.notNull(ctx.getText()),
185         getContext().parseVariableName(
186             ObjectUtils.notNull(ctx.varname().eqname().getText())));
187   }
188 
189   // ====================================================================
190   // For Expressions - https://www.w3.org/TR/xpath-31/#id-for-expressions
191   // ====================================================================
192 
193   @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
194   @Override
195   protected IExpression handleForexpr(Metapath10.ForexprContext ctx) {
196     Metapath10.SimpleforclauseContext simpleForClause = ctx.simpleforclause();
197 
198     // for SimpleForBinding ("," SimpleForBinding)*
199     int bindingCount = simpleForClause.getChildCount() / 2;
200 
201     @NonNull
202     IExpression retval = ObjectUtils.notNull(ctx.exprsingle().accept(this));
203 
204     // step through in reverse
205     for (int idx = bindingCount - 1; idx >= 0; idx--) {
206       Metapath10.SimpleforbindingContext simpleForBinding = simpleForClause.simpleforbinding(idx);
207 
208       Metapath10.VarnameContext varName = simpleForBinding.varname();
209       Metapath10.ExprsingleContext exprSingle = simpleForBinding.exprsingle();
210 
211       IExpression boundExpression = exprSingle.accept(this);
212       assert boundExpression != null;
213 
214       IEnhancedQName qname = getContext().parseVariableName(
215           ObjectUtils.notNull(varName.eqname().getText()));
216 
217       Let.VariableDeclaration variable = new Let.VariableDeclaration(qname, boundExpression);
218 
219       retval = new For(ObjectUtils.notNull(ctx.getText()), variable, retval);
220     }
221     return retval;
222   }
223 
224   // ====================================================================
225   // Let Expressions - https://www.w3.org/TR/xpath-31/#id-let-expressions
226   // ====================================================================
227 
228   @Override
229   protected IExpression handleLet(Metapath10.LetexprContext context) {
230     @NonNull
231     IExpression retval = ObjectUtils.notNull(context.exprsingle().accept(this));
232 
233     Metapath10.SimpleletclauseContext letClause = context.simpleletclause();
234     List<Metapath10.SimpleletbindingContext> clauses = letClause.simpleletbinding();
235 
236     ListIterator<Metapath10.SimpleletbindingContext> reverseListIterator = clauses.listIterator(clauses.size());
237     while (reverseListIterator.hasPrevious()) {
238       Metapath10.SimpleletbindingContext simpleCtx = reverseListIterator.previous();
239 
240       IExpression boundExpression = simpleCtx.exprsingle().accept(this);
241       assert boundExpression != null;
242 
243       IEnhancedQName varName = getContext().parseVariableName(
244           ObjectUtils.notNull(simpleCtx.varname().eqname().getText()));
245 
246       retval = new Let(ObjectUtils.notNull(simpleCtx.getText()), varName, boundExpression, retval); // NOPMD intended
247     }
248     return retval;
249   }
250 
251   // ======================================================================
252   // Map Constructors - https://www.w3.org/TR/xpath-31/#id-map-constructors
253   // ======================================================================
254 
255   @Override
256   protected MapConstructor handleMapConstructor(Metapath10.MapconstructorContext context) {
257     String text = ObjectUtils.notNull(context.getText());
258     return context.getChildCount() == 3
259         // empty
260         ? new MapConstructor(text, CollectionUtil.emptyList())
261         // with members
262         : nairyToCollection(context, 3, 2,
263             (ctx, idx) -> {
264               int pos = (idx - 3) / 2;
265               Metapath10.MapconstructorentryContext entry = ctx.mapconstructorentry(pos);
266               assert entry != null;
267               return new MapConstructor.Entry(
268                   ObjectUtils.notNull(entry.getText()),
269                   ObjectUtils.notNull(entry.mapkeyexpr().accept(this)),
270                   ObjectUtils.notNull(entry.mapvalueexpr().accept(this)));
271             },
272             children -> {
273               assert children != null;
274               return new MapConstructor(text, children);
275             });
276   }
277 
278   // ==============================================================
279   // Array Constructors - https://www.w3.org/TR/xpath-31/#id-arrays
280   // ==============================================================
281 
282   @Override
283   protected IExpression handleArrayConstructor(Metapath10.SquarearrayconstructorContext context) {
284     String text = ObjectUtils.notNull(context.getText());
285     return context.getChildCount() == 2
286         // empty
287         ? new ArraySquareConstructor(text, CollectionUtil.emptyList())
288         // with members
289         : nairyToCollection(context, 1, 2,
290             (ctx, idx) -> {
291               int pos = (idx - 1) / 2;
292               ParseTree tree = ctx.exprsingle(pos);
293               return visit(tree);
294             },
295             children -> {
296               assert children != null;
297               return new ArraySquareConstructor(text, children);
298             });
299   }
300 
301   @Override
302   protected IExpression handleArrayConstructor(Metapath10.CurlyarrayconstructorContext ctx) {
303     return new ArraySequenceConstructor(ObjectUtils.notNull(ctx.getText()), visit(ctx.enclosedexpr()));
304   }
305 
306   // ===============================================
307   // Unary Lookup -
308   // https://www.w3.org/TR/xpath-31/#id-unary-lookup
309   // ===============================================
310 
311   @NonNull
312   private IKeySpecifier toKeySpecifier(@NonNull Metapath10.KeyspecifierContext specifier) {
313     IKeySpecifier keySpecifier;
314     if (specifier.parenthesizedexpr() != null) {
315       keySpecifier = AbstractKeySpecifier.newParenthesizedExprKeySpecifier(
316           ObjectUtils.requireNonNull(specifier.parenthesizedexpr().accept(this)));
317     } else if (specifier.NCName() != null) {
318       keySpecifier = AbstractKeySpecifier.newNameKeySpecifier(
319           ObjectUtils.requireNonNull(specifier.NCName().getText()));
320     } else if (specifier.IntegerLiteral() != null) {
321       keySpecifier = AbstractKeySpecifier.newIntegerLiteralKeySpecifier(
322           IIntegerItem.valueOf(ObjectUtils.requireNonNull(specifier.IntegerLiteral().getText())));
323     } else if (specifier.STAR() != null) {
324       keySpecifier = AbstractKeySpecifier.newWildcardKeySpecifier();
325     } else {
326       throw new UnsupportedOperationException("unknown key specifier");
327     }
328     return keySpecifier;
329   }
330 
331   @Override
332   protected IExpression handleUnarylookup(Metapath10.UnarylookupContext ctx) {
333     return new UnaryLookup(
334         ObjectUtils.notNull(ctx.getText()),
335         toKeySpecifier(ObjectUtils.requireNonNull(ctx.keyspecifier())));
336   }
337 
338   // ====================================================
339   // Parenthesized Expressions -
340   // https://www.w3.org/TR/xpath-31/#id-paren-expressions
341   // ====================================================
342 
343   @Override
344   protected IExpression handleEmptyParenthesizedexpr(Metapath10.ParenthesizedexprContext ctx) {
345     return EmptySequence.instance();
346   }
347 
348   // ==========================================================
349   // Context Item Expression -
350   // https://www.w3.org/TR/xpath-31/#id-context-item-expression
351   // ==========================================================
352 
353   @Override
354   protected IExpression handleContextitemexpr(Metapath10.ContextitemexprContext ctx) {
355     return ContextItem.instance();
356   }
357 
358   // =========================================================================
359   // Static Function Calls - https://www.w3.org/TR/xpath-31/#id-function-calls
360   // =========================================================================
361 
362   /**
363    * Parse a list of arguments.
364    *
365    * @param context
366    *          the argument list AST
367    * @return a stream of CST expressions for each argument, in the original
368    *         argument order
369    */
370   @NonNull
371   protected Stream<IExpression> parseArgumentList(@NonNull Metapath10.ArgumentlistContext context) {
372     int numChildren = context.getChildCount();
373 
374     Stream<IExpression> retval;
375     if (numChildren == 2) {
376       // just the OP CP tokens, which is an empty list
377       retval = Stream.empty();
378     } else {
379       retval = context.argument().stream()
380           .map(argument -> argument.exprsingle().accept(this));
381     }
382     assert retval != null;
383 
384     return retval;
385   }
386 
387   @Override
388   protected IExpression handleFunctioncall(Metapath10.FunctioncallContext ctx) {
389     List<IExpression> arguments = ObjectUtils.notNull(
390         parseArgumentList(ObjectUtils.notNull(ctx.argumentlist()))
391             .collect(Collectors.toUnmodifiableList()));
392 
393     return new StaticFunctionCall(
394         ObjectUtils.notNull(ctx.getText()),
395         () -> getContext().lookupFunction(
396             ObjectUtils.notNull(ctx.eqname().getText()),
397             arguments.size()),
398         arguments);
399   }
400 
401   // ============================================================
402   // https://www.w3.org/TR/xpath-31/#doc-xpath31-NamedFunctionRef
403   // ============================================================
404 
405   @Override
406   public IExpression visitNamedfunctionref(Metapath10.NamedfunctionrefContext ctx) {
407     // Ensure that the default function namespace is used, if needed
408     IEnhancedQName qname = getContext().parseFunctionName(ObjectUtils.notNull(ctx.eqname().getText()));
409     int arity = IIntegerItem.valueOf(ObjectUtils.requireNonNull(ctx.IntegerLiteral().getText()))
410         .toIntValueExact();
411     return new NamedFunctionReference(ObjectUtils.notNull(ctx.getText()), qname, arity);
412   }
413 
414   // ==============================================
415   // https://www.w3.org/TR/xpath-31/#id-inline-func
416   // ==============================================
417 
418   @Override
419   public IExpression handleInlinefunctionexpr(Metapath10.InlinefunctionexprContext context) {
420     // parse the param list
421     List<IArgument> parameters = ObjectUtils.notNull(context.paramlist() == null
422         ? CollectionUtil.emptyList()
423         : nairyToList(
424             ObjectUtils.notNull(context.paramlist()),
425             0,
426             2,
427             (ctx, idx) -> {
428               int pos = idx / 2;
429               Metapath10.ParamContext tree = ctx.param(pos);
430               return IArgument.of(
431                   getContext().parseVariableName(ObjectUtils.notNull(tree.eqname().getText())),
432                   tree.typedeclaration() == null
433                       ? DEFAULT_FUNCTION_SEQUENCE_TYPE
434                       : TypeTestSupport.parseSequenceType(
435                           ObjectUtils.notNull(tree.typedeclaration().sequencetype()),
436                           getContext()));
437             }));
438 
439     // parse the result type
440     ISequenceType resultSequenceType = context.sequencetype() == null
441         ? DEFAULT_FUNCTION_SEQUENCE_TYPE
442         : TypeTestSupport.parseSequenceType(
443             ObjectUtils.notNull(context.sequencetype()),
444             getContext());
445 
446     // parse the function body
447     IExpression body = visit(context.functionbody().enclosedexpr());
448 
449     return new AnonymousFunctionCall(ObjectUtils.notNull(context.getText()), parameters, resultSequenceType, body);
450   }
451 
452   // =========================================================================
453   // Filter Expressions - https://www.w3.org/TR/xpath-31/#id-filter-expression
454   // =========================================================================
455 
456   /**
457    * Parse a predicate AST.
458    *
459    * @param predicate
460    *          the predicate expression
461    * @return the CST expression generated for the predicate
462    */
463   @NonNull
464   protected IExpression parsePredicate(@NonNull Metapath10.PredicateContext predicate) {
465     // the expression is always the second child
466     return visit(predicate.getChild(1));
467   }
468 
469   /**
470    * Parse a series of predicate ASTs.
471    *
472    * @param context
473    *          the parse tree node containing the predicates
474    * @param staringChild
475    *          the first child node corresponding to a predicate
476    * @return the list of CST predicate expressions in the same order as the
477    *         original predicate list
478    */
479   @NonNull
480   protected List<IExpression> parsePredicates(@NonNull ParseTree context, int staringChild) {
481     int numChildren = context.getChildCount();
482     int numPredicates = numChildren - staringChild;
483 
484     List<IExpression> predicates;
485     if (numPredicates == 0) {
486       // no predicates
487       predicates = CollectionUtil.emptyList();
488     } else if (numPredicates == 1) {
489       // single predicate
490       Metapath10.PredicateContext predicate
491           = ObjectUtils.notNull((Metapath10.PredicateContext) context.getChild(staringChild));
492       predicates = CollectionUtil.singletonList(parsePredicate(predicate));
493     } else {
494       // multiple predicates
495       predicates = new ArrayList<>(numPredicates);
496       for (int i = staringChild; i < numChildren; i++) {
497         Metapath10.PredicateContext predicate = ObjectUtils.notNull((Metapath10.PredicateContext) context.getChild(i));
498         predicates.add(parsePredicate(predicate));
499       }
500     }
501     return predicates;
502   }
503 
504   @Override
505   protected IExpression handlePostfixexpr(Metapath10.PostfixexprContext context) {
506     String text = ObjectUtils.notNull(context.getText());
507     return handleGroupedNAiry(
508         context,
509         0,
510         1,
511         (ctx, idx, left) -> {
512           assert left != null;
513 
514           ParseTree tree = ctx.getChild(idx);
515           IExpression result;
516           if (tree instanceof Metapath10.ArgumentlistContext) {
517             // map or array access using function call syntax
518             result = new FunctionCallAccessor(
519                 text,
520                 left,
521                 ObjectUtils.notNull(parseArgumentList((Metapath10.ArgumentlistContext) tree)
522                     .collect(Collectors.toUnmodifiableList())));
523           } else if (tree instanceof Metapath10.PredicateContext) {
524             result = new PredicateExpression(
525                 text,
526                 left,
527                 CollectionUtil.singletonList(parsePredicate((Metapath10.PredicateContext) tree)));
528           } else if (tree instanceof Metapath10.LookupContext) {
529             result = new PostfixLookup(
530                 text,
531                 left,
532                 toKeySpecifier(ObjectUtils.notNull(((Metapath10.LookupContext) tree).keyspecifier())));
533           } else {
534             result = visit(tree);
535           }
536           return result;
537         });
538   }
539 
540   // ======================================================================
541   // Path Expressions - https://www.w3.org/TR/xpath-31/#id-path-expressions
542   // ======================================================================
543 
544   @Override
545   protected IExpression handlePredicate(Metapath10.PredicateContext ctx) {
546     return parsePredicate(ctx);
547   }
548 
549   @Override
550   protected IExpression handleLookup(Metapath10.LookupContext ctx) {
551     throw new UnsupportedOperationException("needs to be implemented");
552   }
553 
554   @Override
555   protected IExpression handlePathexpr(Metapath10.PathexprContext ctx) {
556     int numChildren = ctx.getChildCount();
557 
558     IExpression retval;
559     ParseTree tree = ctx.getChild(0);
560     if (tree instanceof TerminalNode) {
561       String text = ObjectUtils.notNull(ctx.getText());
562       int type = ((TerminalNode) tree).getSymbol().getType();
563       switch (type) {
564       case Metapath10Lexer.SLASH:
565         // a slash expression with optional path
566         if (numChildren == 2) {
567           // the optional path
568           retval = new RootSlashPath(text, visit(ctx.getChild(1)));
569         } else {
570           retval = new RootSlashOnlyPath(text);
571         }
572         break;
573       case Metapath10Lexer.SS:
574         // a double slash expression with path
575         retval = new RootDoubleSlashPath(text, visit(ctx.getChild(1)));
576         break;
577       default:
578         throw new UnsupportedOperationException(((TerminalNode) tree).getSymbol().getText());
579       }
580     } else {
581       // a relative expression or something else
582       retval = visit(tree);
583     }
584     return retval;
585   }
586 
587   // ============================================================
588   // RelativePath Expressions -
589   // https://www.w3.org/TR/xpath-31/#id-relative-path-expressions
590   // ============================================================
591 
592   @Override
593   protected IExpression handleRelativepathexpr(Metapath10.RelativepathexprContext context) {
594     String text = ObjectUtils.notNull(context.getText());
595     return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> {
596       assert left != null;
597 
598       ParseTree operatorTree = ctx.getChild(idx);
599       IExpression right = visit(ctx.getChild(idx + 1));
600 
601       int type = ((TerminalNode) operatorTree).getSymbol().getType();
602 
603       IExpression retval;
604       switch (type) {
605       case Metapath10Lexer.SLASH:
606         retval = new RelativeSlashPath(text, left, right);
607         break;
608       case Metapath10Lexer.SS:
609         retval = new RelativeDoubleSlashPath(text, left, right);
610         break;
611       default:
612         throw new UnsupportedOperationException(((TerminalNode) operatorTree).getSymbol().getText());
613       }
614       return retval;
615     });
616   }
617 
618   // ================================================
619   // Steps - https://www.w3.org/TR/xpath-31/#id-steps
620   // ================================================
621 
622   @Override
623   protected IExpression handleForwardstep(Metapath10.ForwardstepContext ctx) {
624     Metapath10.AbbrevforwardstepContext abbrev = ctx.abbrevforwardstep();
625     String text = ObjectUtils.notNull(ctx.getText());
626     Step retval;
627     if (abbrev == null) {
628       assert ctx.getChildCount() == 2;
629 
630       Token token = (Token) ctx.forwardaxis().getChild(0).getPayload();
631 
632       Axis axis;
633       switch (token.getType()) {
634       case Metapath10Lexer.KW_SELF:
635         axis = Axis.SELF;
636         break;
637       case Metapath10Lexer.KW_CHILD:
638         axis = Axis.CHILDREN;
639         break;
640       case Metapath10Lexer.KW_DESCENDANT:
641         axis = Axis.DESCENDANT;
642         break;
643       case Metapath10Lexer.KW_DESCENDANT_OR_SELF:
644         axis = Axis.DESCENDANT_OR_SELF;
645         break;
646       case Metapath10Lexer.KW_FLAG:
647         axis = Axis.FLAG;
648         break;
649       case Metapath10Lexer.KW_FOLLOWING_SIBLING:
650         axis = Axis.FOLLOWING_SIBLING;
651         break;
652       case Metapath10Lexer.KW_FOLLOWING:
653         axis = Axis.FOLLOWING;
654         break;
655       default:
656         throw new UnsupportedOperationException(token.getText());
657       }
658       retval = new Step(text, axis, parseNodeTest(ctx.nodetest(), false));
659     } else {
660       retval = new Step(text, Axis.CHILDREN, parseNodeTest(ctx.nodetest(), abbrev.AT() != null));
661     }
662     return retval;
663   }
664 
665   @Override
666   protected IExpression handleReversestep(Metapath10.ReversestepContext ctx) {
667     assert ctx.getChildCount() == 2;
668     Token token = (Token) ctx.reverseaxis().getChild(0).getPayload();
669 
670     Axis axis;
671     switch (token.getType()) {
672     case Metapath10Lexer.KW_PARENT:
673       axis = Axis.PARENT;
674       break;
675     case Metapath10Lexer.KW_ANCESTOR:
676       axis = Axis.ANCESTOR;
677       break;
678     case Metapath10Lexer.KW_ANCESTOR_OR_SELF:
679       axis = Axis.ANCESTOR_OR_SELF;
680       break;
681     case Metapath10Lexer.KW_PRECEDING_SIBLING:
682       axis = Axis.PRECEDING_SIBLING;
683       break;
684     case Metapath10Lexer.KW_PRECEDING:
685       axis = Axis.PRECEDING;
686       break;
687     default:
688       throw new UnsupportedOperationException(token.getText());
689     }
690     return new Step(ObjectUtils.notNull(ctx.getText()), axis, parseNodeTest(ctx.nodetest(), false));
691   }
692 
693   // =======================================================
694   // Node Tests - https://www.w3.org/TR/xpath-31/#node-tests
695   // =======================================================
696 
697   /**
698    * Parse an antlr node test expression.
699    *
700    * @param ctx
701    *          the antrl context
702    * @param flag
703    *          if the context is within a flag's scope
704    * @return the resulting expression
705    */
706   @NonNull
707   protected INodeTestExpression parseNodeTest(Metapath10.NodetestContext ctx, boolean flag) {
708     INodeTestExpression retval;
709     if (ctx.kindtest() != null) {
710       IItemType itemType = TypeTestSupport.parseKindTest(
711           ObjectUtils.notNull(ctx.kindtest()),
712           getContext());
713       retval = new KindNodeTest(ObjectUtils.notNull(ctx.getText()), itemType);
714     } else {
715       Metapath10.NametestContext nameTestCtx = ctx.nametest();
716       retval = parseNameTest(nameTestCtx, flag);
717     }
718     return retval;
719   }
720 
721   /**
722    * Parse an antlr name test expression.
723    *
724    * @param ctx
725    *          the antrl context
726    * @param flag
727    *          if the context is within a flag's scope
728    * @return the resulting expression
729    */
730   @NonNull
731   protected INodeTestExpression parseNameTest(Metapath10.NametestContext ctx, boolean flag) {
732     ParseTree testType = ObjectUtils.requireNonNull(ctx.getChild(0));
733 
734     StaticContext staticContext = getContext();
735 
736     INodeTestExpression retval;
737     if (testType instanceof Metapath10.EqnameContext) {
738       String name = ObjectUtils.notNull(ctx.eqname().getText());
739       IEnhancedQName qname = flag
740           ? staticContext.parseFlagName(name)
741           : staticContext.parseModelName(name);
742 
743       String text = ObjectUtils.notNull(ctx.getText());
744       if (!flag
745           && qname.getNamespace().isEmpty()
746           && staticContext.isUseWildcardWhenNamespaceNotDefaulted()) {
747         // Use a wildcard namespace
748         retval = new WildcardNodeTest(text, IWildcardMatcher.anyNamespace(ObjectUtils.notNull(qname.getLocalName())));
749       } else {
750         retval = new NameNodeTest(text, qname);
751       }
752     } else { // wildcard
753       retval = handleWildcard((Metapath10.WildcardContext) testType);
754     }
755     return retval;
756   }
757 
758   @Override
759   protected WildcardNodeTest handleWildcard(Metapath10.WildcardContext ctx) {
760     IWildcardMatcher matcher = null;
761     if (ctx.STAR() == null) {
762       if (ctx.CS() != null) {
763         // specified prefix, any local-name
764         String prefix = ObjectUtils.notNull(ctx.NCName().getText());
765         String namespace = getContext().lookupNamespaceForPrefix(prefix);
766         if (namespace == null) {
767           throw new IllegalStateException(String.format("Prefix '%s' did not map to a namespace.", prefix));
768         }
769         matcher = IWildcardMatcher.anyLocalName(namespace);
770       } else if (ctx.SC() != null) {
771         // any prefix, specified local-name
772         matcher = IWildcardMatcher.anyNamespace(ObjectUtils.notNull(ctx.NCName().getText()));
773       } else {
774         // specified braced namespace, any local-name
775         String bracedUriLiteral = ctx.BracedURILiteral().getText();
776         String namespace = ObjectUtils.notNull(bracedUriLiteral.substring(2, bracedUriLiteral.length() - 1));
777         matcher = IWildcardMatcher.anyLocalName(namespace);
778       }
779     } // star needs no matcher: any prefix, any local-name
780 
781     return new WildcardNodeTest(ObjectUtils.notNull(ctx.getText()), matcher);
782   }
783 
784   // ======================================================================
785   // Predicates within Steps - https://www.w3.org/TR/xpath-31/#id-predicate
786   // ======================================================================
787 
788   @Override
789   protected IExpression handleAxisstep(Metapath10.AxisstepContext ctx) {
790     IExpression step = visit(ctx.getChild(0));
791     ParseTree predicateTree = ctx.getChild(1);
792     assert predicateTree != null;
793 
794     List<IExpression> predicates = parsePredicates(predicateTree, 0);
795 
796     return predicates.isEmpty() ? step : new PredicateExpression(ObjectUtils.notNull(ctx.getText()), step, predicates);
797   }
798 
799   // ===========================================================
800   // Abbreviated Syntax - https://www.w3.org/TR/xpath-31/#abbrev
801   // ===========================================================
802 
803   @Override
804   protected IExpression handleAbbrevforwardstep(Metapath10.AbbrevforwardstepContext ctx) {
805     int numChildren = ctx.getChildCount();
806 
807     String text = ObjectUtils.notNull(ctx.getText());
808     IExpression retval;
809     if (numChildren == 1) {
810       retval = new ModelInstanceStep(text, parseNodeTest(ctx.nodetest(), false));
811     } else {
812       // this is an AT test
813       retval = new FlagStep(text, parseNodeTest(ctx.nodetest(), true));
814     }
815     return retval;
816   }
817 
818   @Override
819   protected IExpression handleAbbrevreversestep(Metapath10.AbbrevreversestepContext ctx) {
820     return new Step(ObjectUtils.notNull(ctx.getText()), Axis.PARENT);
821   }
822 
823   // ======================================================================
824   // Constructing Sequences - https://www.w3.org/TR/xpath-31/#construct_seq
825   // ======================================================================
826 
827   @Override
828   protected IExpression handleRangeexpr(Metapath10.RangeexprContext ctx) {
829     assert ctx.getChildCount() == 3;
830 
831     IExpression left = visit(ctx.getChild(0));
832     IExpression right = visit(ctx.getChild(2));
833 
834     return new Range(ObjectUtils.notNull(ctx.getText()), left, right);
835   }
836 
837   // ========================================================================
838   // Combining Node Sequences - https://www.w3.org/TR/xpath-31/#combining_seq
839   // ========================================================================
840 
841   @Override
842   protected IExpression handleUnionexpr(Metapath10.UnionexprContext ctx) {
843     return handleNAiryCollection(ctx, children -> {
844       assert children != null;
845       return new Union(ObjectUtils.notNull(ctx.getText()), children);
846     });
847   }
848 
849   @Override
850   protected IExpression handleIntersectexceptexpr(Metapath10.IntersectexceptexprContext context) {
851     return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> {
852       assert left != null;
853 
854       ParseTree operatorTree = ctx.getChild(idx);
855       IExpression right = visit(ctx.getChild(idx + 1));
856 
857       int type = ((TerminalNode) operatorTree).getSymbol().getType();
858       String text = ObjectUtils.notNull(ctx.getText());
859       IExpression retval;
860       switch (type) {
861       case Metapath10Lexer.KW_INTERSECT:
862         retval = new Intersect(text, left, right);
863         break;
864       case Metapath10Lexer.KW_EXCEPT:
865         retval = new Except(text, left, right);
866         break;
867       default:
868         throw new UnsupportedOperationException(((TerminalNode) operatorTree).getSymbol().getText());
869       }
870       return retval;
871     });
872   }
873 
874   // ======================================================================
875   // Arithmetic Expressions - https://www.w3.org/TR/xpath-31/#id-arithmetic
876   // ======================================================================
877 
878   @Override
879   protected IExpression handleAdditiveexpr(Metapath10.AdditiveexprContext context) {
880     return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> {
881       ParseTree operatorTree = ctx.getChild(idx);
882       ParseTree rightTree = ctx.getChild(idx + 1);
883       IExpression right = rightTree.accept(this);
884 
885       assert left != null;
886       assert right != null;
887 
888       int type = ((TerminalNode) operatorTree).getSymbol().getType();
889       String text = ObjectUtils.notNull(ctx.getText());
890 
891       IExpression retval;
892       switch (type) {
893       case Metapath10Lexer.PLUS:
894         retval = new Addition(text, left, right);
895         break;
896       case Metapath10Lexer.MINUS:
897         retval = new Subtraction(text, left, right);
898         break;
899       default:
900         throw new UnsupportedOperationException(((TerminalNode) operatorTree).getSymbol().getText());
901       }
902       return retval;
903     });
904   }
905 
906   @Override
907   protected IExpression handleMultiplicativeexpr(Metapath10.MultiplicativeexprContext context) {
908     return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> {
909       assert left != null;
910 
911       ParseTree operatorTree = ctx.getChild(idx);
912       IExpression right = visit(ctx.getChild(idx + 1));
913 
914       assert right != null;
915 
916       int type = ((TerminalNode) operatorTree).getSymbol().getType();
917       String text = ObjectUtils.notNull(ctx.getText());
918       IExpression retval;
919       switch (type) {
920       case Metapath10Lexer.STAR:
921         retval = new Multiplication(text, left, right);
922         break;
923       case Metapath10Lexer.KW_DIV:
924         retval = new Division(text, left, right);
925         break;
926       case Metapath10Lexer.KW_IDIV:
927         retval = new IntegerDivision(text, left, right);
928         break;
929       case Metapath10Lexer.KW_MOD:
930         retval = new Modulo(text, left, right);
931         break;
932       default:
933         throw new UnsupportedOperationException(((TerminalNode) operatorTree).getSymbol().getText());
934       }
935       return retval;
936     });
937   }
938 
939   @Override
940   protected IExpression handleUnaryexpr(Metapath10.UnaryexprContext ctx) {
941     int numChildren = ctx.getChildCount();
942     int negateCount = 0;
943 
944     int idx = 0;
945     for (; idx < numChildren - 1; idx++) {
946       ParseTree tree = ctx.getChild(idx);
947       int type = ((TerminalNode) tree).getSymbol().getType();
948       switch (type) {
949       case Metapath10Lexer.PLUS:
950         break;
951       case Metapath10Lexer.MINUS:
952         negateCount++;
953         break;
954       default:
955         throw new UnsupportedOperationException(((TerminalNode) tree).getSymbol().getText());
956       }
957     }
958 
959     IExpression retval = visit(ctx.getChild(idx));
960     if (negateCount % 2 != 0) {
961       retval = new Negate(ObjectUtils.notNull(ctx.getText()), retval);
962     }
963     return retval;
964   }
965 
966   // =====================================================
967   // String Concatenation Expressions -
968   // https://www.w3.org/TR/xpath-31/#id-string-concat-expr
969   // =====================================================
970 
971   @Override
972   protected IExpression handleStringconcatexpr(Metapath10.StringconcatexprContext ctx) {
973     return handleNAiryCollection(ctx, children -> {
974       assert children != null;
975       return new StringConcat(ObjectUtils.notNull(ctx.getText()), children);
976     });
977   }
978 
979   // =======================================================================
980   // Comparison Expressions - https://www.w3.org/TR/xpath-31/#id-comparisons
981   // =======================================================================
982 
983   @Override
984   protected IExpression handleComparisonexpr(Metapath10.ComparisonexprContext ctx) { // NOPMD - ok
985     assert ctx.getChildCount() == 3;
986 
987     IExpression left = visit(ctx.getChild(0));
988     IExpression right = visit(ctx.getChild(2));
989 
990     // the operator
991     ParseTree operatorTree = ctx.getChild(1);
992     Object payload = operatorTree.getPayload();
993     String text = ObjectUtils.notNull(ctx.getText());
994     ComparisonFunctions.Operator operator;
995     IBooleanLogicExpression retval;
996     if (payload instanceof Metapath10.GeneralcompContext) {
997       Metapath10.GeneralcompContext compContext = (Metapath10.GeneralcompContext) payload;
998       int type = ((TerminalNode) compContext.getChild(0)).getSymbol().getType();
999       switch (type) {
1000       case Metapath10Lexer.EQ:
1001         operator = ComparisonFunctions.Operator.EQ;
1002         break;
1003       case Metapath10Lexer.NE:
1004         operator = ComparisonFunctions.Operator.NE;
1005         break;
1006       case Metapath10Lexer.LT:
1007         operator = ComparisonFunctions.Operator.LT;
1008         break;
1009       case Metapath10Lexer.LE:
1010         operator = ComparisonFunctions.Operator.LE;
1011         break;
1012       case Metapath10Lexer.GT:
1013         operator = ComparisonFunctions.Operator.GT;
1014         break;
1015       case Metapath10Lexer.GE:
1016         operator = ComparisonFunctions.Operator.GE;
1017         break;
1018       default:
1019         throw new UnsupportedOperationException(((TerminalNode) compContext.getChild(0)).getSymbol().getText());
1020       }
1021       retval = new GeneralComparison(text, left, operator, right);
1022     } else if (payload instanceof Metapath10.ValuecompContext) {
1023       Metapath10.ValuecompContext compContext = (Metapath10.ValuecompContext) payload;
1024       int type = ((TerminalNode) compContext.getChild(0)).getSymbol().getType();
1025       switch (type) {
1026       case Metapath10Lexer.KW_EQ:
1027         operator = ComparisonFunctions.Operator.EQ;
1028         break;
1029       case Metapath10Lexer.KW_NE:
1030         operator = ComparisonFunctions.Operator.NE;
1031         break;
1032       case Metapath10Lexer.KW_LT:
1033         operator = ComparisonFunctions.Operator.LT;
1034         break;
1035       case Metapath10Lexer.KW_LE:
1036         operator = ComparisonFunctions.Operator.LE;
1037         break;
1038       case Metapath10Lexer.KW_GT:
1039         operator = ComparisonFunctions.Operator.GT;
1040         break;
1041       case Metapath10Lexer.KW_GE:
1042         operator = ComparisonFunctions.Operator.GE;
1043         break;
1044       default:
1045         throw new UnsupportedOperationException(((TerminalNode) compContext.getChild(0)).getSymbol().getText());
1046       }
1047       retval = new ValueComparison(text, left, operator, right);
1048     } else {
1049       throw new UnsupportedOperationException();
1050     }
1051     return retval;
1052   }
1053 
1054   // ============================================================================
1055   // Logical Expressions - https://www.w3.org/TR/xpath-31/#id-logical-expressions
1056   // ============================================================================
1057 
1058   @Override
1059   protected IExpression handleOrexpr(Metapath10.OrexprContext ctx) {
1060     return handleNAiryCollection(ctx, children -> {
1061       assert children != null;
1062       return new Or(ObjectUtils.notNull(ctx.getText()), children);
1063     });
1064   }
1065 
1066   @Override
1067   protected IExpression handleAndexpr(Metapath10.AndexprContext ctx) {
1068     return handleNAiryCollection(ctx, children -> {
1069       assert children != null;
1070       return new And(ObjectUtils.notNull(ctx.getText()), children);
1071     });
1072   }
1073 
1074   // =========================================================================
1075   // Conditional Expressions - https://www.w3.org/TR/xpath-31/#id-conditionals
1076   // =========================================================================
1077 
1078   @Override
1079   protected IExpression handleIfexpr(Metapath10.IfexprContext ctx) {
1080     IExpression testExpr = visit(ctx.expr());
1081     IExpression thenExpr = visit(ctx.exprsingle(0));
1082     IExpression elseExpr = visit(ctx.exprsingle(1));
1083 
1084     return new If(ObjectUtils.notNull(ctx.getText()), testExpr, thenExpr, elseExpr);
1085   }
1086 
1087   // =========================================================
1088   // Quantified Expressions -
1089   // https://www.w3.org/TR/xpath-31/#id-quantified-expressions
1090   // =========================================================
1091 
1092   @Override
1093   protected IExpression handleQuantifiedexpr(Metapath10.QuantifiedexprContext ctx) {
1094     Quantified.Quantifier quantifier;
1095     int type = ((TerminalNode) ctx.getChild(0)).getSymbol().getType();
1096     switch (type) {
1097     case Metapath10Lexer.KW_SOME:
1098       quantifier = Quantified.Quantifier.SOME;
1099       break;
1100     case Metapath10Lexer.KW_EVERY:
1101       quantifier = Quantified.Quantifier.EVERY;
1102       break;
1103     default:
1104       throw new UnsupportedOperationException(((TerminalNode) ctx.getChild(0)).getSymbol().getText());
1105     }
1106 
1107     int numVars = (ctx.getChildCount() - 2) / 5; // children - "satisfies expr" / ", $ varName in expr"
1108     Map<IEnhancedQName, IExpression> vars = new LinkedHashMap<>(); // NOPMD ordering needed
1109     int offset = 0;
1110     for (; offset < numVars; offset++) {
1111       // $
1112       IEnhancedQName varName = getContext().parseVariableName(ObjectUtils.notNull(
1113           ctx.varname(offset).eqname().getText()));
1114 
1115       // in
1116       IExpression varExpr = visit(ctx.exprsingle(offset));
1117 
1118       vars.put(varName, varExpr);
1119     }
1120 
1121     IExpression satisfies = visit(ctx.exprsingle(offset));
1122 
1123     return new Quantified(ObjectUtils.notNull(ctx.getText()), quantifier, vars, satisfies);
1124   }
1125 
1126   /*
1127    * ============================================================ instance of -
1128    * https://www.w3.org/TR/xpath-31/#id-instance-of
1129    * ============================================================
1130    */
1131 
1132   /**
1133    * Handle the provided expression.
1134    *
1135    * @param ctx
1136    *          the provided expression context
1137    * @return the result
1138    */
1139   @Override
1140   protected IExpression handleInstanceofexpr(@NonNull Metapath10.InstanceofexprContext ctx) {
1141     IExpression left = visit(ctx.treatexpr());
1142     ISequenceType sequenceType = TypeTestSupport.parseSequenceType(
1143         ObjectUtils.notNull(ctx.sequencetype()),
1144         getContext());
1145     return new InstanceOf(ObjectUtils.notNull(ctx.getText()), left, sequenceType);
1146   }
1147 
1148   // ==============================================
1149   // cast - https://www.w3.org/TR/xpath-31/#id-cast
1150   // ==============================================
1151 
1152   @Override
1153   protected IExpression handleCastexpr(Metapath10.CastexprContext ctx) {
1154     IExpression left = visit(ctx.arrowexpr());
1155 
1156     Metapath10.SingletypeContext singleType = ObjectUtils.notNull(ctx.singletype());
1157 
1158     boolean allowEmptySequence = singleType.QM() != null;
1159 
1160     IAtomicOrUnionType<?> type = getTypeForCast(ObjectUtils.notNull(singleType.simpletypename().getText()));
1161 
1162     return new Cast(ObjectUtils.notNull(ctx.getText()), left, type, allowEmptySequence);
1163   }
1164 
1165   // ==================================================
1166   // castable - https://www.w3.org/TR/xpath-31/#id-cast
1167   // ==================================================
1168 
1169   @NonNull
1170   private IAtomicOrUnionType<?> getTypeForCast(@NonNull String name) {
1171     IAtomicOrUnionType<?> type;
1172     try {
1173       type = getContext().lookupAtomicType(name);
1174     } catch (StaticMetapathException ex) {
1175       if (StaticMetapathException.UNKNOWN_TYPE == ex.getErrorCode().getCode()) {
1176         throw new StaticMetapathException(
1177             StaticMetapathException.CAST_UNKNOWN_TYPE,
1178             String.format("Unknown type '%s'.", name),
1179             ex);
1180       }
1181       throw ex;
1182     }
1183 
1184     if (IItemType.anyAtomic().equals(type)) {
1185       throw new StaticMetapathException(
1186           StaticMetapathException.CAST_ANY_ATOMIC,
1187           String.format("Type cannot be '%s',", IItemType.anyAtomic()));
1188     }
1189     return type;
1190   }
1191 
1192   @Override
1193   protected IExpression handleCastableexpr(Metapath10.CastableexprContext ctx) {
1194     IExpression left = visit(ctx.castexpr());
1195 
1196     Metapath10.SingletypeContext singleType = ObjectUtils.notNull(ctx.singletype());
1197 
1198     boolean allowEmptySequence = singleType.QM() != null;
1199 
1200     IAtomicOrUnionType<?> type = getTypeForCast(ObjectUtils.notNull(singleType.simpletypename().getText()));
1201 
1202     return new Castable(ObjectUtils.notNull(ctx.getText()), left, type, allowEmptySequence);
1203   }
1204 
1205   // ================================================
1206   // treat - https://www.w3.org/TR/xpath-31/#id-treat
1207   // ================================================
1208 
1209   @Override
1210   protected IExpression handleTreatexpr(Metapath10.TreatexprContext ctx) {
1211     IExpression left = visit(ctx.castableexpr());
1212 
1213     ISequenceType sequenceType = TypeTestSupport.parseSequenceType(
1214         ObjectUtils.notNull(ctx.sequencetype()),
1215         getContext());
1216 
1217     return new Treat(ObjectUtils.notNull(ctx.getText()), left, sequenceType);
1218   }
1219 
1220   // =========================================================================
1221   // Simple map operator (!) - https://www.w3.org/TR/xpath-31/#id-map-operator
1222   // =========================================================================
1223 
1224   @Override
1225   protected IExpression handleSimplemapexpr(Metapath10.SimplemapexprContext context) {
1226     return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> {
1227       assert left != null;
1228 
1229       // the next child is "!"
1230       assert "!".equals(ctx.getChild(idx).getText());
1231       IExpression right = ObjectUtils.notNull(ctx.getChild(idx + 1).accept(this));
1232 
1233       return new SimpleMap(ObjectUtils.notNull(ctx.getText()), left, right);
1234     });
1235   }
1236 
1237   // =======================================================================
1238   // Arrow operator (=>) - https://www.w3.org/TR/xpath-31/#id-arrow-operator
1239   // =======================================================================
1240 
1241   @Override
1242   protected IExpression handleArrowexpr(Metapath10.ArrowexprContext context) {
1243     return handleGroupedNAiry(context, 0, 3, (ctx, idx, left) -> {
1244       // the next child is "=>"
1245       assert "=>".equals(ctx.getChild(idx).getText());
1246 
1247       int offset = (idx - 1) / 3;
1248 
1249       Metapath10.ArgumentlistContext argumentCtx = ctx.getChild(Metapath10.ArgumentlistContext.class, offset);
1250 
1251       try (Stream<IExpression> args = Stream.concat(
1252           Stream.of(left),
1253           parseArgumentList(ObjectUtils.notNull(argumentCtx)))) {
1254         assert args != null;
1255 
1256         // prepend the focus
1257         List<IExpression> arguments = ObjectUtils.notNull(args
1258             .collect(Collectors.toUnmodifiableList()));
1259 
1260         Metapath10.ArrowfunctionspecifierContext arrowCtx
1261             = ctx.getChild(Metapath10.ArrowfunctionspecifierContext.class, offset);
1262         if (arrowCtx.eqname() != null) {
1263           // named function
1264           return new StaticFunctionCall(
1265               ObjectUtils.notNull(arrowCtx.getText()),
1266               () -> getContext().lookupFunction(ObjectUtils.notNull(arrowCtx.eqname().getText()), arguments.size()),
1267               arguments);
1268         }
1269 
1270         IExpression result;
1271         if (arrowCtx.varref() != null) {
1272           // function instance or name reference
1273           result = new VariableReference(
1274               ObjectUtils.notNull(arrowCtx.getText()),
1275               getContext().parseVariableName(
1276                   ObjectUtils.notNull(arrowCtx.varref().varname().eqname().getText())));
1277         } else if (arrowCtx.parenthesizedexpr() != null) {
1278           // function expression
1279           result = visit(arrowCtx.parenthesizedexpr().expr());
1280         } else {
1281           // TODO: Is this the correct exception to throw here?
1282           throw new InvalidMetapathGrammarException(
1283               String.format("Unable to get function name using arrow specifier '%s'.", arrowCtx.getText()));
1284         }
1285 
1286         return new DynamicFunctionCall(
1287             ObjectUtils.notNull(arrowCtx.getText()),
1288             result,
1289             arguments);
1290       }
1291     });
1292   }
1293 }