View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  //CSOFF: FileLength
18  package org.apache.commons.jexl3.internal;
19  
20  import java.util.ArrayDeque;
21  import java.util.Iterator;
22  import java.util.Objects;
23  import java.util.Queue;
24  import java.util.concurrent.Callable;
25  import java.util.function.Consumer;
26  
27  import org.apache.commons.jexl3.JexlArithmetic;
28  import org.apache.commons.jexl3.JexlContext;
29  import org.apache.commons.jexl3.JexlEngine;
30  import org.apache.commons.jexl3.JexlException;
31  import org.apache.commons.jexl3.JexlOperator;
32  import org.apache.commons.jexl3.JexlOptions;
33  import org.apache.commons.jexl3.JexlScript;
34  import org.apache.commons.jexl3.JxltEngine;
35  import org.apache.commons.jexl3.introspection.JexlMethod;
36  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
37  import org.apache.commons.jexl3.parser.*;
38  
39  /**
40   * An interpreter of JEXL syntax.
41   *
42   * @since 2.0
43   */
44  public class Interpreter extends InterpreterBase {
45      /**
46       * An annotated call.
47       */
48      public class AnnotatedCall implements Callable<Object> {
49          /** The statement. */
50          private final ASTAnnotatedStatement stmt;
51          /** The child index. */
52          private final int index;
53          /** The data. */
54          private final Object data;
55          /** Tracking whether we processed the annotation. */
56          private boolean processed;
57  
58          /**
59           * Simple ctor.
60           * @param astmt the statement
61           * @param aindex the index
62           * @param adata the data
63           */
64          AnnotatedCall(final ASTAnnotatedStatement astmt, final int aindex, final Object adata) {
65              stmt = astmt;
66              index = aindex;
67              data = adata;
68          }
69  
70          @Override
71          public Object call() throws Exception {
72              processed = true;
73              try {
74                  return processAnnotation(stmt, index, data);
75              } catch (JexlException.Return | JexlException.Break | JexlException.Continue xreturn) {
76                  return xreturn;
77              }
78          }
79  
80          /**
81           * @return the actual statement.
82           */
83          public Object getStatement() {
84              return stmt;
85          }
86  
87          /**
88           * @return whether the statement has been processed
89           */
90          public boolean isProcessed() {
91              return processed;
92          }
93      }
94      /**
95       * The thread local interpreter.
96       */
97      protected static final ThreadLocal<Interpreter> INTER =
98                         new ThreadLocal<>();
99      /** Frame height. */
100     protected int fp;
101 
102     /** Symbol values. */
103     protected final Frame frame;
104 
105     /** Block micro-frames. */
106     protected LexicalFrame block;
107 
108     /**
109      * Creates an interpreter.
110      * @param engine   the engine creating this interpreter
111      * @param aContext the evaluation context, global variables, methods and functions
112      * @param opts     the evaluation options, flags modifying evaluation behavior
113      * @param eFrame   the evaluation frame, arguments and local variables
114      */
115     protected Interpreter(final Engine engine, final JexlOptions opts, final JexlContext aContext, final Frame eFrame) {
116         super(engine, opts, aContext);
117         this.frame = eFrame;
118     }
119 
120     /**
121      * Copy constructor.
122      * @param ii  the interpreter to copy
123      * @param jexla the arithmetic instance to use (or null)
124      */
125     protected Interpreter(final Interpreter ii, final JexlArithmetic jexla) {
126         super(ii, jexla);
127         frame = ii.frame;
128         block = ii.block != null ? new LexicalFrame(ii.block) : null;
129     }
130 
131     /**
132      * Calls a method (or function).
133      * <p>
134      * Method resolution is a follows:
135      * 1 - attempt to find a method in the target passed as parameter;
136      * 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target;
137      * 3 - if this fails, narrow the arguments and try again 1
138      * 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument;
139      * </p>
140      * *duck-callable: an object where a "call" function exists
141      *
142      * @param node    the method node
143      * @param target  the target of the method, what it should be invoked upon
144      * @param funcNode the object carrying the method or function or the method identifier
145      * @param argNode the node carrying the arguments
146      * @return the result of the method invocation
147      */
148     protected Object call(final JexlNode node, final Object target, final Object funcNode, final ASTArguments argNode) {
149         cancelCheck(node);
150         // evaluate the arguments
151         final Object[] argv = visit(argNode, null);
152         final String methodName;
153         boolean cacheable = cache;
154         boolean isavar = false;
155         Object functor = funcNode;
156         // get the method name if identifier
157         if (functor instanceof ASTIdentifier) {
158             // function call, target is context or namespace (if there was one)
159             final ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
160             final int symbol = methodIdentifier.getSymbol();
161             methodName = methodIdentifier.getName();
162             functor = null;
163             // is it a global or local variable?
164             if (target == context) {
165                 if (frame != null && frame.has(symbol)) {
166                     functor = frame.get(symbol);
167                     isavar = functor != null;
168                 } else if (context.has(methodName)) {
169                     functor = context.get(methodName);
170                     isavar = functor != null;
171                 }
172                 // name is a variable, can't be cached
173                 cacheable &= !isavar;
174             }
175         } else if (functor instanceof ASTIdentifierAccess) {
176             // a method call on target
177             methodName = ((ASTIdentifierAccess) functor).getName();
178             functor = null;
179             cacheable = true;
180         } else if (functor != null) {
181             // ...(x)(y)
182             methodName = null;
183             cacheable = false;
184         } else if (!node.isSafeLhs(isSafe())) {
185             return unsolvableMethod(node, "?(...)");
186         } else {
187             // safe lhs
188             return null;
189         }
190 
191         // solving the call site
192         final CallDispatcher call = new CallDispatcher(node, cacheable);
193         try {
194             // do we have a cached version method/function name?
195             final Object eval = call.tryEval(target, methodName, argv);
196             if (JexlEngine.TRY_FAILED != eval) {
197                 return eval;
198             }
199             boolean functorp = false;
200             boolean narrow = false;
201             // pseudo loop to try acquiring methods without and with argument narrowing
202             while (true) {
203                 call.narrow = narrow;
204                 // direct function or method call
205                 if (functor == null || functorp) {
206                     // try a method or function from context
207                     if (call.isTargetMethod(target, methodName, argv)) {
208                         return call.eval(methodName);
209                     }
210                     if (target == context) {
211                         // solve 'null' namespace
212                         final Object namespace = resolveNamespace(null, node);
213                         if (namespace != null
214                             && namespace != context
215                             && call.isTargetMethod(namespace, methodName, argv)) {
216                             return call.eval(methodName);
217                         }
218                         // do not try context function since this was attempted
219                         // 10 lines above...; solve as an arithmetic function
220                         if (call.isArithmeticMethod(methodName, argv)) {
221                             return call.eval(methodName);
222                         }
223                         // could not find a method, try as a property of a non-context target (performed once)
224                     } else {
225                         // try prepending target to arguments and look for
226                         // applicable method in context...
227                         final Object[] pargv = functionArguments(target, narrow, argv);
228                         if (call.isContextMethod(methodName, pargv)) {
229                             return call.eval(methodName);
230                         }
231                         // ...or arithmetic
232                         if (call.isArithmeticMethod(methodName, pargv)) {
233                             return call.eval(methodName);
234                         }
235                         // the method may also be a functor stored in a property of the target
236                         if (!narrow) {
237                             final JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
238                             if (get != null) {
239                                 functor = get.tryInvoke(target, methodName);
240                                 functorp = functor != null;
241                             }
242                         }
243                     }
244                 }
245                 // this may happen without the above when we are chaining call like x(a)(b)
246                 // or when a var/symbol or antish var is used as a "function" name
247                 if (functor != null) {
248                     // lambda, script or jexl method will do
249                     if (functor instanceof JexlScript) {
250                         return ((JexlScript) functor).execute(context, argv);
251                     }
252                     if (functor instanceof JexlMethod) {
253                         return ((JexlMethod) functor).invoke(target, argv);
254                     }
255                     final String mCALL = "call";
256                     // maybe a generic callable, try a 'call' method
257                     if (call.isTargetMethod(functor, mCALL, argv)) {
258                         return call.eval(mCALL);
259                     }
260                     // functor is a var, may be method is a global one?
261                     if (isavar) {
262                         if (call.isContextMethod(methodName, argv)) {
263                             return call.eval(methodName);
264                         }
265                         if (call.isArithmeticMethod(methodName, argv)) {
266                             return call.eval(methodName);
267                         }
268                     }
269                     // try prepending functor to arguments and look for
270                     // context or arithmetic function called 'call'
271                     final Object[] pargv = functionArguments(functor, narrow, argv);
272                     if (call.isContextMethod(mCALL, pargv)) {
273                         return call.eval(mCALL);
274                     }
275                     if (call.isArithmeticMethod(mCALL, pargv)) {
276                         return call.eval(mCALL);
277                     }
278                 }
279                 // if we did not find an exact method by name and we haven't tried yet,
280                 // attempt to narrow the parameters and if this succeeds, try again in next loop
281                 if (narrow || !arithmetic.narrowArguments(argv)) {
282                     break;
283                 }
284                 narrow = true;
285                 // continue;
286             }
287         }
288         catch (final JexlException.TryFailed xany) {
289             throw invocationException(node, methodName, xany);
290         }
291         catch (final JexlException xthru) {
292             if (xthru.getInfo() != null) {
293                 throw xthru;
294             }
295         }
296         catch (final Exception xany) {
297             throw invocationException(node, methodName, xany);
298         }
299         // we have either evaluated and returned or no method was found
300         return node.isSafeLhs(isSafe())
301                 ? null
302                 : unsolvableMethod(node, methodName, argv);
303     }
304 
305     /**
306      * Evaluate the catch in a try/catch/finally.
307      *
308      * @param catchVar the variable containing the exception
309      * @param catchBody the body
310      * @param caught the caught exception
311      * @param data the data
312      * @return the result of body evaluation
313      */
314     private Object evalCatch(final ASTReference catchVar, final JexlNode catchBody,
315                              final JexlException caught, final Object data) {
316         // declare catch variable and assign with caught exception
317         final ASTIdentifier catchVariable = (ASTIdentifier) catchVar.jjtGetChild(0);
318         final int symbol = catchVariable.getSymbol();
319         final boolean lexical = catchVariable.isLexical() || options.isLexical();
320         if (lexical) {
321             // create lexical frame
322             final LexicalFrame locals = new LexicalFrame(frame, block);
323             // it may be a local previously declared
324             final boolean trySymbol = symbol >= 0 && catchVariable instanceof ASTVar;
325             if (trySymbol && !defineVariable((ASTVar) catchVariable, locals)) {
326                 return redefinedVariable(catchVar.jjtGetParent(), catchVariable.getName());
327             }
328             block = locals;
329         }
330         if (symbol < 0) {
331             setContextVariable(catchVar.jjtGetParent(), catchVariable.getName(), caught);
332         } else {
333             final Throwable cause  = caught.getCause();
334             frame.set(symbol, cause == null? caught : cause);
335         }
336         try {
337             // evaluate body
338             return catchBody.jjtAccept(this, data);
339         } finally {
340             // restore lexical frame
341             if (lexical) {
342                 block = block.pop();
343             }
344         }
345     }
346 
347     @Override
348     protected Object visit(final ASTJxltLiteral node, final Object data) {
349         return evalJxltHandle(node);
350     }
351 
352     /**
353      * Evaluates an access identifier based on the 2 main implementations;
354      * static (name or numbered identifier) or dynamic (jxlt).
355      * @param node the identifier access node
356      * @return the evaluated identifier
357      */
358     private Object evalIdentifier(final ASTIdentifierAccess node) {
359         if (!(node instanceof ASTIdentifierAccessJxlt)) {
360             return node.getIdentifier();
361         }
362         final ASTIdentifierAccessJxlt jxltNode = (ASTIdentifierAccessJxlt) node;
363         Throwable cause = null;
364         try {
365             final Object name = evalJxltHandle(jxltNode);
366             if (name != null) {
367                 return name;
368             }
369         } catch (final JxltEngine.Exception xjxlt) {
370             cause = xjxlt;
371         }
372         return node.isSafe() ? null : unsolvableProperty(jxltNode, jxltNode.getExpressionSource(), true, cause);
373     }
374 
375     /**
376      * Evaluates a JxltHandle node.
377      * <p>This parses and stores the JXLT template if necessary (upon first execution)</p>
378      * @param node the node
379      * @return the JXLT template evaluation.
380      * @param <NODE> the node type
381      */
382     private <NODE extends JexlNode & JexlNode.JxltHandle> Object evalJxltHandle(final NODE node) {
383         final JxltEngine.Expression expr = node.getExpression();
384         // internal classes to evaluate in context
385         if (expr instanceof TemplateEngine.TemplateExpression) {
386             final Object eval = ((TemplateEngine.TemplateExpression) expr).evaluate(context, frame, options);
387             if (eval != null) {
388                 final String inter = eval.toString();
389                 if (options.isStrictInterpolation()) {
390                     return inter;
391                 }
392                 final Integer id = JexlArithmetic.parseIdentifier(inter);
393                 return id != null ? id : eval;
394             }
395         }
396         return null;
397     }
398 
399     /**
400      * Executes an assignment with an optional side effect operator.
401      * @param node     the node
402      * @param assignop the assignment operator or null if simply assignment
403      * @param data     the data
404      * @return the left hand side
405      */
406     protected Object executeAssign(final JexlNode node, final JexlOperator assignop, final Object data) { // CSOFF: MethodLength
407         cancelCheck(node);
408         // left contains the reference to assign to
409         final JexlNode left = node.jjtGetChild(0);
410         final ASTIdentifier variable;
411         Object object = null;
412         final int symbol;
413         // check var decl with assign is ok
414         if (left instanceof ASTIdentifier) {
415             variable = (ASTIdentifier) left;
416             symbol = variable.getSymbol();
417             if (symbol >= 0) {
418                 if  (variable.isLexical() || options.isLexical()) {
419                     if (variable instanceof ASTVar) {
420                         if (!defineVariable((ASTVar) variable, block)) {
421                             return redefinedVariable(variable, variable.getName());
422                         }
423                     } else if (variable.isShaded() && (variable.isLexical() || options.isLexicalShade())) {
424                         return undefinedVariable(variable, variable.getName());
425                     }
426                 }
427                 if (variable.isCaptured() && options.isConstCapture()) {
428                     return constVariable(variable, variable.getName());
429                 }
430             }
431         } else {
432             variable = null;
433             symbol = -1;
434         }
435         boolean antish = options.isAntish();
436         // 0: determine initial object & property:
437         final int last = left.jjtGetNumChildren() - 1;
438         // right is the value expression to assign
439        final  Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
440         // actual value to return, right in most cases
441         Object actual = right;
442         // a (var?) v = ... expression
443         if (variable != null) {
444             if (symbol >= 0) {
445                 // check we are not assigning a symbol itself
446                 if (last < 0) {
447                     if (assignop == null) {
448                         // make the closure accessible to itself, ie capture the currently set variable after frame creation
449                         if (right instanceof Closure) {
450                             final Closure closure = (Closure) right;
451                             // the variable scope must be the parent of the lambdas
452                             closure.captureSelfIfRecursive(frame, symbol);
453                         }
454                         frame.set(symbol, right);
455                     } else {
456                         // go through potential overload
457                         final Object self = getVariable(frame, block, variable);
458                         final Consumer<Object> f = r -> frame.set(symbol, r);
459                         actual = operators.tryAssignOverload(node, assignop, f, self, right);
460                     }
461                     return actual; // 1
462                 }
463                 object = getVariable(frame, block, variable);
464                 // top level is a symbol, cannot be an antish var
465                 antish = false;
466             } else {
467                 // check we are not assigning direct global
468                 final String name = variable.getName();
469                 if (last < 0) {
470                     if (assignop == null) {
471                         setContextVariable(node, name, right);
472                     } else {
473                         // go through potential overload
474                         final Object self = context.get(name);
475                         final Consumer<Object> f = r ->  setContextVariable(node, name, r);
476                         actual = operators.tryAssignOverload(node, assignop, f, self, right);
477                     }
478                     return actual; // 2
479                 }
480                 object = context.get(name);
481                 // top level accesses object, cannot be an antish var
482                 if (object != null) {
483                     antish = false;
484                 }
485             }
486         } else if (!(left instanceof ASTReference)) {
487             throw new JexlException(left, "illegal assignment form 0");
488         }
489         // 1: follow children till penultimate, resolve dot/array
490         JexlNode objectNode = null;
491         StringBuilder ant = null;
492         int v = 1;
493         // start at 1 if symbol
494         main: for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
495             objectNode = left.jjtGetChild(c);
496             object = objectNode.jjtAccept(this, object);
497             if (object != null) {
498                 // disallow mixing antish variable & bean with same root; avoid ambiguity
499                 antish = false;
500             } else if (antish) {
501                 // initialize if first time
502                 if (ant == null) {
503                     final JexlNode first = left.jjtGetChild(0);
504                     final ASTIdentifier firstId = first instanceof ASTIdentifier
505                             ? (ASTIdentifier) first
506                             : null;
507                     if (firstId == null || firstId.getSymbol() >= 0) {
508                         // ant remains null, object is null, stop solving
509                         antish = false;
510                         break main;
511                     }
512                     ant = new StringBuilder(firstId.getName());
513                 }
514                 // catch up to current child
515                 for (; v <= c; ++v) {
516                     final JexlNode child = left.jjtGetChild(v);
517                     final ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess
518                             ? (ASTIdentifierAccess) child
519                             : null;
520                     // remain antish only if unsafe navigation
521                     if (aid == null || aid.isSafe() || aid.isExpression()) {
522                         antish = false;
523                         break main;
524                     }
525                     ant.append('.');
526                     ant.append(aid.getName());
527                 }
528                 // solve antish
529                 object = context.get(ant.toString());
530             } else {
531                 throw new JexlException(objectNode, "illegal assignment form");
532             }
533         }
534         // 2: last objectNode will perform assignment in all cases
535         JexlNode propertyNode = left.jjtGetChild(last);
536         final ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess
537                 ? (ASTIdentifierAccess) propertyNode
538                 : null;
539         final Object property;
540         if (propertyId != null) {
541             // deal with creating/assigning antish variable
542             if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
543                 ant.append('.');
544                 ant.append(propertyId.getName());
545                 final String name = ant.toString();
546                 if (assignop == null) {
547                     setContextVariable(propertyNode, name, right);
548                 } else {
549                     final Object self = context.get(ant.toString());
550                     final JexlNode pnode = propertyNode;
551                     final Consumer<Object> assign = r -> setContextVariable(pnode, name, r);
552                     actual = operators.tryAssignOverload(node, assignop, assign, self, right);
553                 }
554                 return actual; // 3
555             }
556             // property of an object ?
557             property = evalIdentifier(propertyId);
558         } else if (propertyNode instanceof ASTArrayAccess) {
559             // can have multiple nodes - either an expression, integer literal or reference
560             final int numChildren = propertyNode.jjtGetNumChildren() - 1;
561             for (int i = 0; i < numChildren; i++) {
562                 final JexlNode nindex = propertyNode.jjtGetChild(i);
563                 final Object index = nindex.jjtAccept(this, null);
564                 object = getAttribute(object, index, nindex);
565             }
566             propertyNode = propertyNode.jjtGetChild(numChildren);
567             property = propertyNode.jjtAccept(this, null);
568         } else {
569             throw new JexlException(objectNode, "illegal assignment form");
570         }
571         // we may have a null property as in map[null], no check needed.
572         // we cannot *have* a null object though.
573         if (object == null) {
574             // no object, we fail
575             return unsolvableProperty(objectNode, "<null>.<?>", true, null);
576         }
577         // 3: one before last, assign
578         if (assignop == null) {
579             setAttribute(object, property, right, propertyNode);
580         } else {
581             final Object self = getAttribute(object, property, propertyNode);
582             final Object o = object;
583             final JexlNode n = propertyNode;
584             final Consumer<Object> assign = r ->  setAttribute(o, property, r, n);
585             actual = operators.tryAssignOverload(node, assignop, assign, self, right);
586         }
587         return actual;
588     }
589 
590     private Object forIterator(final ASTForeachStatement node, final Object data) {
591         Object result = null;
592         /* first objectNode is the loop variable */
593         final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
594         final ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
595         final int symbol = loopVariable.getSymbol();
596         final boolean lexical = loopVariable.isLexical() || options.isLexical();
597         final LexicalFrame locals = lexical? new LexicalFrame(frame, block) : null;
598         final boolean loopSymbol = symbol >= 0 && loopVariable instanceof ASTVar;
599         if (lexical) {
600             // create lexical frame
601             // it may be a local previously declared
602             if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
603                 return redefinedVariable(node, loopVariable.getName());
604             }
605             block = locals;
606         }
607         Object forEach = null;
608         try {
609             /* second objectNode is the variable to iterate */
610             final Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
611             // make sure there is a value to iterate upon
612             if (iterableValue == null) {
613                 return null;
614             }
615             /* last child node is the statement to execute */
616             final int numChildren = node.jjtGetNumChildren();
617             final JexlNode statement = numChildren >= 3 ? node.jjtGetChild(numChildren - 1) : null;
618             // get an iterator for the collection/array/etc. via the introspector.
619             forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
620             final Iterator<?> itemsIterator = forEach instanceof Iterator
621                     ? (Iterator<?>) forEach
622                     : uberspect.getIterator(iterableValue);
623             if (itemsIterator == null) {
624                 return null;
625             }
626             int cnt = 0;
627             while (itemsIterator.hasNext()) {
628                 cancelCheck(node);
629                 // reset loop variable
630                 if (lexical && cnt++ > 0) {
631                     // clean up but remain current
632                     block.pop();
633                     // unlikely to fail
634                     if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
635                         return redefinedVariable(node, loopVariable.getName());
636                     }
637                 }
638                 // set loopVariable to value of iterator
639                 final Object value = itemsIterator.next();
640                 if (symbol < 0) {
641                     setContextVariable(node, loopVariable.getName(), value);
642                 } else {
643                     frame.set(symbol, value);
644                 }
645                 if (statement != null) {
646                     try {
647                         // execute statement
648                         result = statement.jjtAccept(this, data);
649                     } catch (final JexlException.Break stmtBreak) {
650                         break;
651                     } catch (final JexlException.Continue stmtContinue) {
652                         //continue;
653                     }
654                 }
655             }
656         } finally {
657             //  closeable iterator handling
658             closeIfSupported(forEach);
659             // restore lexical frame
660             if (lexical) {
661                 block = block.pop();
662             }
663         }
664         return result;
665     }
666 
667     private Object forLoop(final ASTForeachStatement node, final Object data) {
668         Object result = null;
669         int nc;
670         final int form = node.getLoopForm();
671         final LexicalFrame locals;
672         /* first child node might be the loop variable */
673         if ((form & 1) != 0) {
674             nc = 1;
675             final JexlNode init = node.jjtGetChild(0);
676             ASTVar loopVariable = null;
677             if (init instanceof ASTAssignment) {
678                 final JexlNode child = init.jjtGetChild(0);
679                 if (child instanceof ASTVar) {
680                     loopVariable = (ASTVar) child;
681                 }
682             } else if (init instanceof  ASTVar){
683                 loopVariable = (ASTVar) init;
684             }
685             if (loopVariable != null) {
686                 final boolean lexical = loopVariable.isLexical() || options.isLexical();
687                 locals = lexical ? new LexicalFrame(frame, block) : null;
688                 if (locals != null) {
689                     block = locals;
690                 }
691             } else {
692                 locals = null;
693             }
694             // initialize after eventual creation of local lexical frame
695             init.jjtAccept(this, data);
696             // other inits
697             for (JexlNode moreAssignment = node.jjtGetChild(nc);
698                  moreAssignment instanceof ASTAssignment;
699                  moreAssignment = node.jjtGetChild(++nc)) {
700                 moreAssignment.jjtAccept(this, data);
701             }
702         } else {
703             locals = null;
704             nc = 0;
705         }
706         try {
707             // the loop condition
708             final JexlNode predicate = (form & 2) != 0? node.jjtGetChild(nc++) : null;
709             // the loop step
710             final JexlNode step = (form & 4) != 0? node.jjtGetChild(nc++) : null;
711             // last child is body
712             final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
713             // while(predicate())...
714             while (predicate == null || testPredicate(predicate, predicate.jjtAccept(this, data))) {
715                 cancelCheck(node);
716                 // the body
717                 if (statement != null) {
718                     try {
719                         // execute statement
720                         result = statement.jjtAccept(this, data);
721                     } catch (final JexlException.Break stmtBreak) {
722                         break;
723                     } catch (final JexlException.Continue stmtContinue) {
724                         //continue;
725                     }
726                 }
727                 // the step
728                 if (step != null) {
729                     step.jjtAccept(this, data);
730                 }
731             }
732         } finally {
733             // restore lexical frame
734             if (locals != null) {
735                 block = block.pop();
736             }
737         }
738         return result;
739     }
740 
741     /**
742      * Interpret the given script/expression.
743      * <p>
744      * If the underlying JEXL engine is silent, errors will be logged through
745      * its logger as warning.
746      * @param node the script or expression to interpret.
747      * @return the result of the interpretation.
748      * @throws JexlException if any error occurs during interpretation.
749      */
750     public Object interpret(final JexlNode node) {
751         JexlContext.ThreadLocal tcontext = null;
752         JexlEngine tjexl = null;
753         Interpreter tinter = null;
754         try {
755             tinter = putThreadInterpreter(this);
756             if (tinter != null) {
757                 fp = tinter.fp + 1;
758             }
759             if (context instanceof JexlContext.ThreadLocal) {
760                 tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
761             }
762             tjexl = jexl.putThreadEngine(jexl);
763             if (fp > jexl.stackOverflow) {
764                 throw new JexlException.StackOverflow(node.jexlInfo(), "jexl (" + jexl.stackOverflow + ")", null);
765             }
766             cancelCheck(node);
767             return arithmetic.controlReturn(node.jjtAccept(this, null));
768         } catch (final StackOverflowError xstack) {
769             final JexlException xjexl = new JexlException.StackOverflow(node.jexlInfo(), "jvm", xstack);
770             if (!isSilent()) {
771                 throw xjexl.clean();
772             }
773             if (logger.isWarnEnabled()) {
774                 logger.warn(xjexl.getMessage(), xjexl.getCause());
775             }
776         } catch (final JexlException.Return xreturn) {
777             return xreturn.getValue();
778         } catch (final JexlException.Cancel xcancel) {
779             // cancelled |= Thread.interrupted();
780             cancelled.weakCompareAndSet(false, Thread.interrupted());
781             if (isCancellable()) {
782                 throw xcancel.clean();
783             }
784         } catch (final JexlException xjexl) {
785             if (!isSilent()) {
786                 throw xjexl.clean();
787             }
788             if (logger.isWarnEnabled()) {
789                 logger.warn(xjexl.getMessage(), xjexl.getCause());
790             }
791         } finally {
792             // clean functors at top level
793             if (fp == 0) {
794                 synchronized (this) {
795                     if (functors != null) {
796                         for (final Object functor : functors.values()) {
797                             closeIfSupported(functor);
798                         }
799                         functors.clear();
800                         functors = null;
801                     }
802                 }
803             }
804             jexl.putThreadEngine(tjexl);
805             if (context instanceof JexlContext.ThreadLocal) {
806                 jexl.putThreadLocal(tcontext);
807             }
808             if (tinter != null) {
809                 fp = tinter.fp - 1;
810             }
811             putThreadInterpreter(tinter);
812         }
813         return null;
814     }
815 
816     /**
817      * Determines if the specified Object is assignment-compatible with the object represented by the Class.
818      * @param object the Object
819      * @param clazz the Class
820      * @return the result of isInstance call
821      */
822     private boolean isInstance(final Object object, final Object clazz) {
823         if (object == null || clazz == null) {
824             return false;
825         }
826         final Class<?> c = clazz instanceof Class<?>
827             ? (Class<?>) clazz
828             : uberspect.getClassByName(resolveClassName(clazz.toString()));
829         return c != null && c.isInstance(object);
830     }
831 
832     /**
833      * Processes an annotated statement.
834      * @param stmt the statement
835      * @param index the index of the current annotation being processed
836      * @param data the contextual data
837      * @return  the result of the statement block evaluation
838      */
839     protected Object processAnnotation(final ASTAnnotatedStatement stmt, final int index, final Object data) {
840         // are we evaluating the block ?
841         final int last = stmt.jjtGetNumChildren() - 1;
842         if (index == last) {
843             final JexlNode cblock = stmt.jjtGetChild(last);
844             // if the context has changed, might need a new interpreter
845             final JexlArithmetic jexla = arithmetic.options(context);
846             if (jexla == arithmetic) {
847                 return cblock.jjtAccept(Interpreter.this, data);
848             }
849             if (!arithmetic.getClass().equals(jexla.getClass()) && logger.isWarnEnabled()) {
850                 logger.warn("expected arithmetic to be " + arithmetic.getClass().getSimpleName()
851                         + ", got " + jexla.getClass().getSimpleName()
852                 );
853             }
854             final Interpreter ii = new Interpreter(Interpreter.this, jexla);
855             final Object r = cblock.jjtAccept(ii, data);
856             if (ii.isCancelled()) {
857                 Interpreter.this.cancel();
858             }
859             return r;
860         }
861         // tracking whether we processed the annotation
862         final AnnotatedCall jstmt = new AnnotatedCall(stmt, index + 1, data);
863         // the annotation node and name
864         final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index);
865         final String aname = anode.getName();
866         // evaluate the arguments
867         final Object[] argv = anode.jjtGetNumChildren() > 0
868                         ? visit((ASTArguments) anode.jjtGetChild(0), null) : null;
869         // wrap the future, will recurse through annotation processor
870         Object result;
871         try {
872             result = processAnnotation(aname, argv, jstmt);
873             // not processing an annotation is an error
874             if (!jstmt.isProcessed()) {
875                 return annotationError(anode, aname, null);
876             }
877         } catch (final JexlException xany) {
878             throw xany;
879         } catch (final Exception xany) {
880             return annotationError(anode, aname, xany);
881         }
882         // the caller may return a return, break or continue
883         if (result instanceof JexlException) {
884             throw (JexlException) result;
885         }
886         return result;
887     }
888 
889     /**
890      * Delegates the annotation processing to the JexlContext if it is an AnnotationProcessor.
891      * @param annotation    the annotation name
892      * @param args          the annotation arguments
893      * @param stmt          the statement / block that was annotated
894      * @return the result of statement.call()
895      * @throws Exception if anything goes wrong
896      */
897     protected Object processAnnotation(final String annotation, final Object[] args, final Callable<Object> stmt) throws Exception {
898                 return context instanceof JexlContext.AnnotationProcessor
899                 ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
900                 : stmt.call();
901     }
902 
903     /**
904      * Swaps the current thread local interpreter.
905      * @param inter the interpreter or null
906      * @return the previous thread local interpreter
907      */
908     protected Interpreter putThreadInterpreter(final Interpreter inter) {
909         final Interpreter pinter = INTER.get();
910         INTER.set(inter);
911         return pinter;
912     }
913 
914     /**
915      * Resolves a class name.
916      * @param name the simple class name
917      * @return the fully qualified class name or the name
918      */
919     private String resolveClassName(final String name) {
920         // try with local solver
921         String fqcn = fqcnSolver.resolveClassName(name);
922         if (fqcn != null) {
923             return fqcn;
924         }
925         // context may be solving class name?
926         if (context instanceof JexlContext.ClassNameResolver) {
927             final JexlContext.ClassNameResolver resolver = (JexlContext.ClassNameResolver) context;
928             fqcn = resolver.resolveClassName(name);
929             if (fqcn != null) {
930                 return fqcn;
931             }
932         }
933         return name;
934     }
935 
936     /**
937      * Runs a closure.
938      * @param closure the closure
939      * @return the closure return value
940      */
941     protected Object runClosure(final Closure closure) {
942         final ASTJexlScript script = closure.getScript();
943         // if empty script, nothing to evaluate
944         final int numChildren = script.jjtGetNumChildren();
945         if (numChildren == 0) {
946             return null;
947         }
948         block = new LexicalFrame(frame, block).defineArgs();
949         try {
950             final JexlNode body = script instanceof ASTJexlLambda
951                     ? script.jjtGetChild(numChildren - 1)
952                     : script;
953             return interpret(body);
954         } finally {
955             block = block.pop();
956         }
957     }
958 
959     private boolean testPredicate(final JexlNode node, final Object condition) {
960         final Object predicate = operators.tryOverload(node, JexlOperator.CONDITION, condition);
961         return  arithmetic.testPredicate(predicate != JexlEngine.TRY_FAILED? predicate : condition);
962     }
963 
964     @Override
965     protected Object visit(final ASTAddNode node, final Object data) {
966         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
967         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
968         try {
969             final Object result = operators.tryOverload(node, JexlOperator.ADD, left, right);
970             return result != JexlEngine.TRY_FAILED ? result : arithmetic.add(left, right);
971         } catch (final ArithmeticException xrt) {
972             throw new JexlException(findNullOperand(node, left, right), "+ error", xrt);
973         }
974     }
975 
976     /**
977      * Short-circuit evaluation of logical expression.
978      * @param check the fuse value that will stop evaluation, true for OR, false for AND
979      * @param node a ASTAndNode or a ASTOrNode
980      * @param data the data, usually null and unused
981      * @return true or false if boolean logical option is true, the last evaluated argument otherwise
982      */
983     private Object shortCircuit(final boolean check, final JexlNode node, final Object data) {
984         /*
985          * The pattern for exception mgmt is to let the child*.jjtAccept out of the try/catch loop so that if one fails,
986          * the ex will traverse up to the interpreter. In cases where this is not convenient/possible, JexlException
987          * must be caught explicitly and rethrown.
988          */
989         final int last = node.jjtGetNumChildren();
990         Object argument = null;
991         boolean result = false;
992         for (int c = 0; c < last; ++c) {
993             argument = node.jjtGetChild(c).jjtAccept(this, data);
994             try {
995                 // short-circuit
996                 result = arithmetic.toBoolean(argument);
997                 if (result == check) {
998                     break;
999                 }
1000             } catch (final ArithmeticException xrt) {
1001                 throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
1002             }
1003         }
1004         return options.isBooleanLogical()? result : argument;
1005     }
1006 
1007     @Override
1008     protected Object visit(final ASTAndNode node, final Object data) {
1009         return shortCircuit(false, node, data);
1010     }
1011 
1012     @Override
1013     protected Object visit(final ASTOrNode node, final Object data) {
1014         return shortCircuit(true, node, data);
1015     }
1016 
1017     @Override
1018     protected Object visit(final ASTAnnotatedStatement node, final Object data) {
1019         return processAnnotation(node, 0, data);
1020     }
1021 
1022     @Override
1023     protected Object visit(final ASTAnnotation node, final Object data) {
1024         throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported.");
1025     }
1026 
1027     @Override
1028     protected Object[] visit(final ASTArguments node, final Object data) {
1029         final int argc = node.jjtGetNumChildren();
1030         final Object[] argv = new Object[argc];
1031         for (int i = 0; i < argc; i++) {
1032             argv[i] = node.jjtGetChild(i).jjtAccept(this, data);
1033         }
1034         return argv;
1035     }
1036 
1037     @Override
1038     protected Object visit(final ASTArrayAccess node, final Object data) {
1039         // first objectNode is the identifier
1040         Object object = data;
1041         // can have multiple nodes - either an expression, integer literal or reference
1042         final int numChildren = node.jjtGetNumChildren();
1043         for (int i = 0; i < numChildren; i++) {
1044             final JexlNode nindex = node.jjtGetChild(i);
1045             if (object == null) {
1046                 // safe navigation access
1047                 return node.isSafeChild(i)
1048                     ? null
1049                     :unsolvableProperty(nindex, stringifyProperty(nindex), false, null);
1050             }
1051             final Object index = nindex.jjtAccept(this, null);
1052             cancelCheck(node);
1053             object = getAttribute(object, index, nindex);
1054         }
1055         return object;
1056     }
1057 
1058     @Override
1059     protected Object visit(final ASTArrayLiteral node, final Object data) {
1060         final int childCount = node.jjtGetNumChildren();
1061         final JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount, node.isExtended());
1062         boolean extended = false;
1063         for (int i = 0; i < childCount; i++) {
1064             cancelCheck(node);
1065             final JexlNode child = node.jjtGetChild(i);
1066             if (child instanceof ASTExtendedLiteral) {
1067                 extended = true;
1068             } else {
1069                 final Object entry = node.jjtGetChild(i).jjtAccept(this, data);
1070                 ab.add(entry);
1071             }
1072         }
1073         return ab.create(extended);
1074     }
1075 
1076     @Override
1077     protected Object visit(final ASTAssignment node, final Object data) {
1078         return executeAssign(node, null, data);
1079     }
1080 
1081     @Override
1082     protected Object visit(final ASTBitwiseAndNode node, final Object data) {
1083         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1084         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1085         try {
1086             final Object result = operators.tryOverload(node, JexlOperator.AND, left, right);
1087             return result != JexlEngine.TRY_FAILED ? result : arithmetic.and(left, right);
1088         } catch (final ArithmeticException xrt) {
1089             throw new JexlException(findNullOperand(node, left, right), "& error", xrt);
1090         }
1091     }
1092 
1093     @Override
1094     protected Object visit(final ASTBitwiseComplNode node, final Object data) {
1095         final Object arg = node.jjtGetChild(0).jjtAccept(this, data);
1096         try {
1097             final Object result = operators.tryOverload(node, JexlOperator.COMPLEMENT, arg);
1098             return result != JexlEngine.TRY_FAILED ? result : arithmetic.complement(arg);
1099         } catch (final ArithmeticException xrt) {
1100             throw new JexlException(node, "~ error", xrt);
1101         }
1102     }
1103 
1104     @Override
1105     protected Object visit(final ASTBitwiseOrNode node, final Object data) {
1106         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1107         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1108         try {
1109             final Object result = operators.tryOverload(node, JexlOperator.OR, left, right);
1110             return result != JexlEngine.TRY_FAILED ? result : arithmetic.or(left, right);
1111         } catch (final ArithmeticException xrt) {
1112             throw new JexlException(findNullOperand(node, left, right), "| error", xrt);
1113         }
1114     }
1115 
1116     @Override
1117     protected Object visit(final ASTBitwiseXorNode node, final Object data) {
1118         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1119         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1120         try {
1121             final Object result = operators.tryOverload(node, JexlOperator.XOR, left, right);
1122             return result != JexlEngine.TRY_FAILED ? result : arithmetic.xor(left, right);
1123         } catch (final ArithmeticException xrt) {
1124             throw new JexlException(findNullOperand(node, left, right), "^ error", xrt);
1125         }
1126     }
1127 
1128     @Override
1129     protected Object visit(final ASTBlock node, final Object data) {
1130         final int cnt = node.getSymbolCount();
1131         if (cnt <= 0) {
1132             return visitBlock(node, data);
1133         }
1134         try {
1135             block = new LexicalFrame(frame, block);
1136             return visitBlock(node, data);
1137         } finally {
1138             block = block.pop();
1139         }
1140     }
1141 
1142     @Override
1143     protected Object visit(final ASTBreak node, final Object data) {
1144         throw new JexlException.Break(node);
1145     }
1146 
1147     @Override
1148     protected Object visit(final ASTConstructorNode node, final Object data) {
1149         if (isCancelled()) {
1150             throw new JexlException.Cancel(node);
1151         }
1152         // first child is class or class name
1153         final Object target = node.jjtGetChild(0).jjtAccept(this, data);
1154         // get the ctor args
1155         final int argc = node.jjtGetNumChildren() - 1;
1156         Object[] argv = new Object[argc];
1157         for (int i = 0; i < argc; i++) {
1158             argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
1159         }
1160 
1161         try {
1162             final boolean cacheable = cache;
1163             // attempt to reuse last funcall cached in volatile JexlNode.value
1164             if (cacheable) {
1165                 final Object cached = node.jjtGetValue();
1166                 if (cached instanceof Funcall) {
1167                     final Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv);
1168                     if (JexlEngine.TRY_FAILED != eval) {
1169                         return eval;
1170                     }
1171                 }
1172             }
1173             boolean narrow = false;
1174             Funcall funcall = null;
1175             JexlMethod ctor;
1176             while (true) {
1177                 // try as stated
1178                 ctor = uberspect.getConstructor(target, argv);
1179                 if (ctor != null) {
1180                     if (cacheable && ctor.isCacheable()) {
1181                         funcall = new Funcall(ctor, narrow);
1182                     }
1183                     break;
1184                 }
1185                 // try with prepending context as first argument
1186                 final Object[] nargv = callArguments(context, narrow, argv);
1187                 ctor = uberspect.getConstructor(target, nargv);
1188                 if (ctor != null) {
1189                     if (cacheable && ctor.isCacheable()) {
1190                         funcall = new ContextualCtor(ctor, narrow);
1191                     }
1192                     argv = nargv;
1193                     break;
1194                 }
1195                 // if we did not find an exact method by name and we haven't tried yet,
1196                 // attempt to narrow the parameters and if this succeeds, try again in next loop
1197                 if (!narrow && arithmetic.narrowArguments(argv)) {
1198                     narrow = true;
1199                     continue;
1200                 }
1201                 // we are done trying
1202                 break;
1203             }
1204             // we have either evaluated and returned or might have found a ctor
1205             if (ctor != null) {
1206                 final Object eval = ctor.invoke(target, argv);
1207                 // cache executor in volatile JexlNode.value
1208                 if (funcall != null) {
1209                     node.jjtSetValue(funcall);
1210                 }
1211                 return eval;
1212             }
1213             final String tstr = Objects.toString(target, "?");
1214             return unsolvableMethod(node, tstr, argv);
1215         } catch (final JexlException.Method xmethod) {
1216             throw xmethod;
1217         } catch (final Exception xany) {
1218             final String tstr = Objects.toString(target, "?");
1219             throw invocationException(node, tstr, xany);
1220         }
1221     }
1222 
1223     @Override
1224     protected Object visit(final ASTCaseStatement node, final Object data) {
1225         final int argc = node.jjtGetNumChildren();
1226         Object result = null;
1227         for (int i = 0; i < argc; i++) {
1228             result = node.jjtGetChild(i).jjtAccept(this, data);
1229         }
1230         return result;
1231     }
1232 
1233     @Override
1234     protected Object visit(final ASTCaseExpression node, final Object data) {
1235         return node.jjtGetChild(0).jjtAccept(this, data);
1236     }
1237 
1238     @Override
1239     protected Object visit(final ASTContinue node, final Object data) {
1240         throw new JexlException.Continue(node);
1241     }
1242 
1243     @Override
1244     protected Object visit(final ASTDecrementGetNode node, final Object data) {
1245         return executeAssign(node, JexlOperator.DECREMENT_AND_GET, data);
1246     }
1247 
1248     @Override
1249     protected Object visit(final ASTDefineVars node, final Object data) {
1250         final int argc = node.jjtGetNumChildren();
1251         Object result = null;
1252         for (int i = 0; i < argc; i++) {
1253             result = node.jjtGetChild(i).jjtAccept(this, data);
1254         }
1255         return result;
1256     }
1257 
1258     @Override
1259     protected Object visit(final ASTDivNode node, final Object data) {
1260         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1261         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1262         try {
1263             final Object result = operators.tryOverload(node, JexlOperator.DIVIDE, left, right);
1264             return result != JexlEngine.TRY_FAILED ? result : arithmetic.divide(left, right);
1265         } catch (final ArithmeticException xrt) {
1266             if (!arithmetic.isStrict()) {
1267                 return 0.0d;
1268             }
1269             throw new JexlException(findNullOperand(node, left, right), "/ error", xrt);
1270         }
1271     }
1272     @Override
1273     protected Object visit(final ASTDoWhileStatement node, final Object data) {
1274         Object result = null;
1275         final int nc = node.jjtGetNumChildren();
1276         /* last objectNode is the condition */
1277         final JexlNode condition = node.jjtGetChild(nc - 1);
1278         do {
1279             cancelCheck(node);
1280             if (nc > 1) {
1281                 try {
1282                     // execute statement
1283                     result = node.jjtGetChild(0).jjtAccept(this, data);
1284                 } catch (final JexlException.Break stmtBreak) {
1285                     break;
1286                 } catch (final JexlException.Continue stmtContinue) {
1287                     //continue;
1288                 }
1289             }
1290         } while (testPredicate(condition, condition.jjtAccept(this, data)));
1291         return result;
1292     }
1293 
1294     @Override
1295     protected Object visit(final ASTEmptyFunction node, final Object data) {
1296         try {
1297             final Object value = node.jjtGetChild(0).jjtAccept(this, data);
1298             return operators.empty(node, value);
1299         } catch (final JexlException xany) {
1300             return true;
1301         }
1302     }
1303 
1304     @Override
1305     protected Object visit(final ASTEQNode node, final Object data) {
1306         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1307         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1308         try {
1309             final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
1310             return result != JexlEngine.TRY_FAILED ? result : arithmetic.equals(left, right);
1311         } catch (final ArithmeticException xrt) {
1312             throw new JexlException(findNullOperand(node, left, right), "== error", xrt);
1313         }
1314     }
1315 
1316     @Override
1317     protected Object visit(final ASTEQSNode node, final Object data) {
1318         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1319         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1320         try {
1321             final Object result = operators.tryOverload(node, JexlOperator.EQSTRICT, left, right);
1322             return result != JexlEngine.TRY_FAILED ? result : arithmetic.strictEquals(left, right);
1323         } catch (final ArithmeticException xrt) {
1324             throw new JexlException(findNullOperand(node, left, right), "=== error", xrt);
1325         }
1326     }
1327 
1328     @Override
1329     protected Object visit(final ASTERNode node, final Object data) {
1330         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1331         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1332         // note the arguments inversion between 'in'/'matches' and 'contains'
1333         // if x in y then y contains x
1334         return operators.contains(node, JexlOperator.CONTAINS, right, left);
1335     }
1336 
1337     @Override
1338     protected Object visit(final ASTEWNode node, final Object data) {
1339         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1340         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1341         return operators.endsWith(node, JexlOperator.ENDSWITH, left, right);
1342     }
1343 
1344     @Override
1345     protected Object visit(final ASTExtendedLiteral node, final Object data) {
1346         return node;
1347     }
1348 
1349     @Override
1350     protected Object visit(final ASTFalseNode node, final Object data) {
1351         return Boolean.FALSE;
1352     }
1353 
1354     @Override
1355     protected Object visit(final ASTForeachStatement node, final Object data) {
1356         return node.getLoopForm() == 0 ? forIterator(node, data) : forLoop(node, data);
1357     }
1358 
1359     @Override
1360     protected Object visit(final ASTFunctionNode node, final Object data) {
1361         final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
1362         final String nsid = functionNode.getNamespace();
1363         final Object namespace = nsid != null? resolveNamespace(nsid, node) : context;
1364         final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
1365         return call(node, namespace, functionNode, argNode);
1366     }
1367 
1368     @Override
1369     protected Object visit(final ASTGENode node, final Object data) {
1370         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1371         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1372         try {
1373             final Object result = operators.tryOverload(node, JexlOperator.GTE, left, right);
1374             return result != JexlEngine.TRY_FAILED
1375                    ? result
1376                    : arithmetic.greaterThanOrEqual(left, right);
1377         } catch (final ArithmeticException xrt) {
1378             throw new JexlException(findNullOperand(node, left, right), ">= error", xrt);
1379         }
1380     }
1381 
1382     @Override
1383     protected Object visit(final ASTGetDecrementNode node, final Object data) {
1384         return executeAssign(node, JexlOperator.GET_AND_DECREMENT, data);
1385     }
1386 
1387     @Override
1388     protected Object visit(final ASTGetIncrementNode node, final Object data) {
1389         return executeAssign(node, JexlOperator.GET_AND_INCREMENT, data);
1390     }
1391 
1392     @Override
1393     protected Object visit(final ASTGTNode node, final Object data) {
1394         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1395         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1396         try {
1397             final Object result = operators.tryOverload(node, JexlOperator.GT, left, right);
1398             return result != JexlEngine.TRY_FAILED
1399                    ? result
1400                    : arithmetic.greaterThan(left, right);
1401         } catch (final ArithmeticException xrt) {
1402             throw new JexlException(findNullOperand(node, left, right), "> error", xrt);
1403         }
1404     }
1405 
1406     @Override
1407     protected Object visit(final ASTIdentifier identifier, final Object data) {
1408         cancelCheck(identifier);
1409         return data != null
1410                 ? getAttribute(data, identifier.getName(), identifier)
1411                 : getVariable(frame, block, identifier);
1412     }
1413 
1414     @Override
1415     protected Object visit(final ASTIdentifierAccess node, final Object data) {
1416         if (data == null) {
1417             return null;
1418         }
1419         final Object id = evalIdentifier(node);
1420         return getAttribute(data, id, node);
1421     }
1422 
1423     @Override
1424     protected Object visit(final ASTIfStatement node, final Object data) {
1425         final int n = 0;
1426         final int numChildren = node.jjtGetNumChildren();
1427         try {
1428             Object result = null;
1429             // pairs of { conditions , 'then' statement }
1430             for(int ifElse = 0; ifElse < numChildren - 1; ifElse += 2) {
1431                 final JexlNode testNode = node.jjtGetChild(ifElse);
1432                 final Object condition = testNode.jjtAccept(this, null);
1433                 if (testPredicate(testNode, condition)) {
1434                     // first objectNode is true statement
1435                     return node.jjtGetChild(ifElse + 1).jjtAccept(this, null);
1436                 }
1437             }
1438             // if odd...
1439             if ((numChildren & 1) == 1) {
1440                 // If there is an else, it is the last child of an odd number of children in the statement,
1441                 // execute it.
1442                 result = node.jjtGetChild(numChildren - 1).jjtAccept(this, null);
1443             }
1444             return result;
1445         } catch (final ArithmeticException xrt) {
1446             throw new JexlException(node.jjtGetChild(n), "if error", xrt);
1447         }
1448     }
1449 
1450     @Override
1451     protected Object visit(final ASTIncrementGetNode node, final Object data) {
1452         return executeAssign(node, JexlOperator.INCREMENT_AND_GET, data);
1453     }
1454 
1455     @Override
1456     protected Object visit(final ASTInstanceOf node, final Object data) {
1457         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1458         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1459         return isInstance(left, right);
1460     }
1461 
1462     @Override
1463     protected Object visit(final ASTJexlScript script, final Object data) {
1464         if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
1465             final Closure closure = new Closure(this, (ASTJexlLambda) script);
1466             // if the function is named, assign in the local frame
1467             final JexlNode child0 = script.jjtGetChild(0);
1468             if (child0 instanceof ASTVar) {
1469                 final ASTVar variable = (ASTVar) child0;
1470                 this.visit(variable, data);
1471                 final int symbol = variable.getSymbol();
1472                 frame.set(symbol, closure);
1473                 // make the closure accessible to itself, ie capture the 'function' variable after frame creation
1474                 closure.captureSelfIfRecursive(frame, symbol);
1475             }
1476             return closure;
1477         }
1478         block = new LexicalFrame(frame, block).defineArgs();
1479         try {
1480             final int numChildren = script.jjtGetNumChildren();
1481             Object result = null;
1482             for (int i = 0; i < numChildren; i++) {
1483                 final JexlNode child = script.jjtGetChild(i);
1484                 result = child.jjtAccept(this, data);
1485                 cancelCheck(child);
1486             }
1487             return result;
1488         } finally {
1489             block = block.pop();
1490         }
1491     }
1492 
1493     @Override
1494     protected Object visit(final ASTLENode node, final Object data) {
1495         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1496         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1497         try {
1498             final Object result = operators.tryOverload(node, JexlOperator.LTE, left, right);
1499             return result != JexlEngine.TRY_FAILED
1500                    ? result
1501                    : arithmetic.lessThanOrEqual(left, right);
1502         } catch (final ArithmeticException xrt) {
1503             throw new JexlException(findNullOperand(node, left, right), "<= error", xrt);
1504         }
1505     }
1506 
1507     @Override
1508     protected Object visit(final ASTLTNode node, final Object data) {
1509         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1510         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1511         try {
1512             final Object result = operators.tryOverload(node, JexlOperator.LT, left, right);
1513             return result != JexlEngine.TRY_FAILED
1514                    ? result
1515                    : arithmetic.lessThan(left, right);
1516         } catch (final ArithmeticException xrt) {
1517             throw new JexlException(findNullOperand(node, left, right), "< error", xrt);
1518         }
1519     }
1520 
1521     @Override
1522     protected Object visit(final ASTMapEntry node, final Object data) {
1523         final Object key = node.jjtGetChild(0).jjtAccept(this, data);
1524         final Object value = node.jjtGetChild(1).jjtAccept(this, data);
1525         return new Object[]{key, value};
1526     }
1527 
1528     @Override
1529     protected Object visit(final ASTMapLiteral node, final Object data) {
1530         final int childCount = node.jjtGetNumChildren();
1531         final JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount, node.isExtended());
1532         for (int i = 0; i < childCount; i++) {
1533             cancelCheck(node);
1534             final JexlNode child = node.jjtGetChild(i);
1535             if (!(child instanceof ASTExtendedLiteral)) {
1536                 final Object[] entry = (Object[]) child.jjtAccept(this, data);
1537                 mb.put(entry[0], entry[1]);
1538             }
1539         }
1540         return mb.create();
1541     }
1542 
1543     @Override
1544     protected Object visit(final ASTMethodNode node, final Object data) {
1545         return visit(node, null, data);
1546     }
1547 
1548     /**
1549      * Execute a method call, ie syntactically written as name.call(...).
1550      * @param node the actual method call node
1551      * @param antish non-null when name.call is an antish variable
1552      * @param data the context
1553      * @return the method call result
1554      */
1555     private Object visit(final ASTMethodNode node, final Object antish, final Object data) {
1556         Object object = antish;
1557         // left contains the reference to the method
1558         final JexlNode methodNode = node.jjtGetChild(0);
1559         Object method;
1560         // 1: determine object and method or functor
1561         if (methodNode instanceof ASTIdentifierAccess) {
1562             method = methodNode;
1563             if (object == null) {
1564                 object = data;
1565                 if (object == null) {
1566                     // no object, we fail
1567                     return node.isSafeLhs(isSafe())
1568                         ? null
1569                         : unsolvableMethod(methodNode, "<null>.<?>(...)");
1570                 }
1571             } else {
1572                 // edge case of antish var used as functor
1573                 method = object;
1574             }
1575         } else {
1576             method = methodNode.jjtAccept(this, data);
1577         }
1578         Object result = method;
1579         for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
1580             if (result == null) {
1581                 // no method, we fail// variable unknown in context and not a local
1582                 return node.isSafeLhs(isSafe())
1583                         ? null
1584                         : unsolvableMethod(methodNode, "<?>.<null>(...)");
1585             }
1586             final ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
1587             result = call(node, object, result, argNode);
1588             object = result;
1589         }
1590         return result;
1591     }
1592 
1593     @Override
1594     protected Object visit(final ASTModNode node, final Object data) {
1595         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1596         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1597         try {
1598             final Object result = operators.tryOverload(node, JexlOperator.MOD, left, right);
1599             return result != JexlEngine.TRY_FAILED ? result : arithmetic.mod(left, right);
1600         } catch (final ArithmeticException xrt) {
1601             if (!arithmetic.isStrict()) {
1602                 return 0.0d;
1603             }
1604             throw new JexlException(findNullOperand(node, left, right), "% error", xrt);
1605         }
1606     }
1607 
1608     @Override
1609     protected Object visit(final ASTMulNode node, final Object data) {
1610         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1611         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1612         try {
1613             final Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, left, right);
1614             return result != JexlEngine.TRY_FAILED ? result : arithmetic.multiply(left, right);
1615         } catch (final ArithmeticException xrt) {
1616             throw new JexlException(findNullOperand(node, left, right), "* error", xrt);
1617         }
1618     }
1619 
1620     @Override
1621     protected Object visit(final ASTNENode node, final Object data) {
1622         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1623         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1624         try {
1625             final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
1626             return result != JexlEngine.TRY_FAILED
1627                    ? !arithmetic.toBoolean(result)
1628                    : !arithmetic.equals(left, right);
1629         } catch (final ArithmeticException xrt) {
1630             throw new JexlException(findNullOperand(node, left, right), "!= error", xrt);
1631         }
1632     }
1633 
1634     @Override
1635     protected Object visit(final ASTNESNode node, final Object data) {
1636         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1637         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1638         try {
1639             final Object result = operators.tryOverload(node, JexlOperator.EQSTRICT, left, right);
1640             return result != JexlEngine.TRY_FAILED
1641                 ? !arithmetic.toBoolean(result)
1642                 : !arithmetic.strictEquals(left, right);
1643         } catch (final ArithmeticException xrt) {
1644             throw new JexlException(findNullOperand(node, left, right), "!== error", xrt);
1645         }
1646     }
1647 
1648     @Override
1649     protected Object visit(final ASTNEWNode node, final Object data) {
1650         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1651         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1652         return operators.endsWith(node, JexlOperator.NOT_ENDSWITH, left, right);
1653     }
1654 
1655     @Override
1656 
1657     protected Object visit(final ASTNotInstanceOf node, final Object data) {
1658         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1659         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1660         return !isInstance(left, right);
1661     }
1662 
1663     @Override
1664     protected Object visit(final ASTNotNode node, final Object data) {
1665         final Object val = node.jjtGetChild(0).jjtAccept(this, data);
1666         try {
1667             final Object result = operators.tryOverload(node, JexlOperator.NOT, val);
1668             return result != JexlEngine.TRY_FAILED ? result : arithmetic.not(val);
1669         } catch (final ArithmeticException xrt) {
1670             throw new JexlException(node, "! error", xrt);
1671         }
1672     }
1673 
1674     @Override
1675     protected Object visit(final ASTNRNode node, final Object data) {
1676         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1677         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1678         // note the arguments inversion between (not) 'in'/'matches' and  (not) 'contains'
1679         // if x not-in y then y not-contains x
1680         return operators.contains(node, JexlOperator.NOT_CONTAINS, right, left);
1681     }
1682 
1683     @Override
1684     protected Object visit(final ASTNSWNode node, final Object data) {
1685         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1686         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1687         return operators.startsWith(node, JexlOperator.NOT_STARTSWITH, left, right);
1688     }
1689 
1690     @Override
1691     protected Object visit(final ASTNullLiteral node, final Object data) {
1692         return null;
1693     }
1694 
1695     @Override
1696     protected Object visit(final ASTNullpNode node, final Object data) {
1697         Object lhs;
1698         try {
1699             lhs = node.jjtGetChild(0).jjtAccept(this, data);
1700         } catch (final JexlException xany) {
1701             if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
1702                 throw xany;
1703             }
1704             lhs = null;
1705         }
1706         // null elision as in "x ?? z"
1707         return lhs != null ? lhs : node.jjtGetChild(1).jjtAccept(this, data);
1708     }
1709 
1710     @Override
1711     protected Object visit(final ASTNumberLiteral node, final Object data) {
1712         if (data != null && node.isInteger()) {
1713             return getAttribute(data, node.getLiteral(), node);
1714         }
1715         return node.getLiteral();
1716     }
1717 
1718     @Override
1719     protected Object visit(final ASTQualifiedIdentifier node, final Object data) {
1720         return resolveClassName(node.getName());
1721     }
1722 
1723     @Override
1724     protected Object visit(final ASTRangeNode node, final Object data) {
1725         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1726         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1727         try {
1728             return arithmetic.createRange(left, right);
1729         } catch (final ArithmeticException xrt) {
1730             throw new JexlException(findNullOperand(node, left, right), ".. error", xrt);
1731         }
1732     }
1733 
1734     @Override
1735     protected Object visit(final ASTReference node, final Object data) {
1736         cancelCheck(node);
1737         final int numChildren = node.jjtGetNumChildren();
1738         final JexlNode parent = node.jjtGetParent();
1739         // pass first piece of data in and loop through children
1740         Object object = null;
1741         JexlNode objectNode = null;
1742         JexlNode ptyNode = null;
1743         StringBuilder ant = null;
1744         boolean antish = !(parent instanceof ASTReference) && options.isAntish();
1745         int v = 1;
1746         main:
1747         for (int c = 0; c < numChildren; c++) {
1748             objectNode = node.jjtGetChild(c);
1749             if (objectNode instanceof ASTMethodNode) {
1750                 antish = false;
1751                 if (object == null) {
1752                     // we may be performing a method call on an antish var
1753                     if (ant != null) {
1754                         final JexlNode child = objectNode.jjtGetChild(0);
1755                         if (child instanceof ASTIdentifierAccess) {
1756                             final int alen = ant.length();
1757                             ant.append('.');
1758                             ant.append(((ASTIdentifierAccess) child).getName());
1759                             object = context.get(ant.toString());
1760                             if (object != null) {
1761                                 object = visit((ASTMethodNode) objectNode, object, context);
1762                                 continue;
1763                             }
1764                             // remove method name from antish
1765                             ant.delete(alen, ant.length());
1766                             ptyNode = objectNode;
1767                         }
1768                     }
1769                     break;
1770                 }
1771             } else if (objectNode instanceof ASTArrayAccess) {
1772                 antish = false;
1773                 if (object == null) {
1774                     ptyNode = objectNode;
1775                     break;
1776                 }
1777             }
1778             // attempt to evaluate the property within the object (visit(ASTIdentifierAccess node))
1779             object = objectNode.jjtAccept(this, object);
1780             cancelCheck(node);
1781             if (object != null) {
1782                 // disallow mixing antish variable & bean with same root; avoid ambiguity
1783                 antish = false;
1784             } else if (antish) {
1785                 // create first from first node
1786                 if (ant == null) {
1787                     // if we still have a null object, check for an antish variable
1788                     final JexlNode first = node.jjtGetChild(0);
1789                     if (!(first instanceof ASTIdentifier)) {
1790                         // not an identifier, not antish
1791                         ptyNode = objectNode;
1792                         break main;
1793                     }
1794                     final ASTIdentifier afirst = (ASTIdentifier) first;
1795                     ant = new StringBuilder(afirst.getName());
1796                     continue;
1797                     // skip the first node case since it was trialed in jjtAccept above and returned null
1798                 }
1799                 // catch up to current node
1800                 for (; v <= c; ++v) {
1801                     final JexlNode child = node.jjtGetChild(v);
1802                     if (!(child instanceof ASTIdentifierAccess)) {
1803                         // not an identifier, not antish
1804                         ptyNode = objectNode;
1805                         break main;
1806                     }
1807                     final ASTIdentifierAccess achild = (ASTIdentifierAccess) child;
1808                     if (achild.isSafe() || achild.isExpression()) {
1809                         break main;
1810                     }
1811                     ant.append('.');
1812                     ant.append(achild.getName());
1813                 }
1814                 // solve antish
1815                 object = context.get(ant.toString());
1816             } else if (c != numChildren - 1) {
1817                 // only the last one may be null
1818                 ptyNode = c == 0 && numChildren > 1 ? node.jjtGetChild(1) : objectNode;
1819                 break; //
1820             }
1821         }
1822         // dealing with null
1823         if (object == null) {
1824             if (ptyNode != null) {
1825                 if (ptyNode.isSafeLhs(isSafe())) {
1826                     return null;
1827                 }
1828                 if (ant != null) {
1829                     final String aname = ant.toString();
1830                     final boolean defined = isVariableDefined(frame, block, aname);
1831                     return unsolvableVariable(node, aname, !defined);
1832                 }
1833                 return unsolvableProperty(node,
1834                         stringifyProperty(ptyNode), ptyNode == objectNode, null);
1835             }
1836             if (antish) {
1837                 if (node.isSafeLhs(isSafe())) {
1838                     return null;
1839                 }
1840                 final String aname = Objects.toString(ant, "?");
1841                 final boolean defined = isVariableDefined(frame, block, aname);
1842                 // defined but null; arg of a strict operator?
1843                 if (defined && !isStrictOperand(node)) {
1844                     return null;
1845                 }
1846                 return unsolvableVariable(node, aname, !defined);
1847             }
1848         }
1849         return object;
1850     }
1851 
1852     @Override
1853     protected Object visit(final ASTReferenceExpression node, final Object data) {
1854         return node.jjtGetChild(0).jjtAccept(this, data);
1855     }
1856 
1857     @Override
1858     protected Object visit(final ASTRegexLiteral node, final Object data) {
1859         return node.getLiteral();
1860     }
1861 
1862     @Override
1863     protected Object visit(final ASTReturnStatement node, final Object data) {
1864         final Object val = node.jjtGetNumChildren() == 1
1865             ? node.jjtGetChild(0).jjtAccept(this, data)
1866             : null;
1867         cancelCheck(node);
1868         final JexlNode parent = node.jjtGetParent();
1869         // if return is last child of script, no need to throw
1870         if (parent instanceof ASTJexlScript &&
1871             parent.jjtGetChild(parent.jjtGetNumChildren() - 1) == node) {
1872             return val;
1873         }
1874         throw new JexlException.Return(node, null, val);
1875     }
1876 
1877     @Override
1878     protected Object visit(final ASTSetAddNode node, final Object data) {
1879         return executeAssign(node, JexlOperator.SELF_ADD, data);
1880     }
1881 
1882     @Override
1883     protected Object visit(final ASTSetAndNode node, final Object data) {
1884         return executeAssign(node, JexlOperator.SELF_AND, data);
1885     }
1886 
1887     @Override
1888     protected Object visit(final ASTSetDivNode node, final Object data) {
1889         return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
1890     }
1891 
1892     @Override
1893     protected Object visit(final ASTSetLiteral node, final Object data) {
1894         final int childCount = node.jjtGetNumChildren();
1895         final JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount, node.isExtended());
1896         for (int i = 0; i < childCount; i++) {
1897             cancelCheck(node);
1898             final JexlNode child = node.jjtGetChild(i);
1899             if (!(child instanceof ASTExtendedLiteral)) {
1900                 final Object entry = child.jjtAccept(this, data);
1901                 mb.add(entry);
1902             }
1903         }
1904         return mb.create();
1905     }
1906 
1907     @Override
1908     protected Object visit(final ASTSetModNode node, final Object data) {
1909         return executeAssign(node, JexlOperator.SELF_MOD, data);
1910     }
1911 
1912     @Override
1913     protected Object visit(final ASTSetMultNode node, final Object data) {
1914         return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
1915     }
1916 
1917     @Override
1918     protected Object visit(final ASTSetOrNode node, final Object data) {
1919         return executeAssign(node, JexlOperator.SELF_OR, data);
1920     }
1921 
1922     @Override
1923     protected Object visit(final ASTSetShiftLeftNode node, final Object data) {
1924         return executeAssign(node, JexlOperator.SELF_SHIFTLEFT, data);
1925     }
1926 
1927     @Override
1928     protected Object visit(final ASTSetShiftRightNode node, final Object data) {
1929         return executeAssign(node, JexlOperator.SELF_SHIFTRIGHT, data);
1930     }
1931 
1932     @Override
1933     protected Object visit(final ASTSetShiftRightUnsignedNode node, final Object data) {
1934         return executeAssign(node, JexlOperator.SELF_SHIFTRIGHTU, data);
1935     }
1936 
1937     @Override
1938     protected Object visit(final ASTSetSubNode node, final Object data) {
1939         return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
1940     }
1941 
1942     @Override
1943     protected Object visit(final ASTSetXorNode node, final Object data) {
1944         return executeAssign(node, JexlOperator.SELF_XOR, data);
1945     }
1946 
1947     @Override
1948     protected Object visit(final ASTShiftLeftNode node, final Object data) {
1949         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1950         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1951         try {
1952             final Object result = operators.tryOverload(node, JexlOperator.SHIFTLEFT, left, right);
1953             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftLeft(left, right);
1954         } catch (final ArithmeticException xrt) {
1955             throw new JexlException(findNullOperand(node, left, right), "<< error", xrt);
1956         }
1957     }
1958 
1959     @Override
1960     protected Object visit(final ASTShiftRightNode node, final Object data) {
1961         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1962         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1963         try {
1964             final Object result = operators.tryOverload(node, JexlOperator.SHIFTRIGHT, left, right);
1965             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRight(left, right);
1966         } catch (final ArithmeticException xrt) {
1967             throw new JexlException(findNullOperand(node, left, right), ">> error", xrt);
1968         }
1969     }
1970 
1971     @Override
1972     protected Object visit(final ASTShiftRightUnsignedNode node, final Object data) {
1973         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1974         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1975         try {
1976             final Object result = operators.tryOverload(node, JexlOperator.SHIFTRIGHTU, left, right);
1977             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRightUnsigned(left, right);
1978         } catch (final ArithmeticException xrt) {
1979             throw new JexlException(findNullOperand(node, left, right), ">>> error", xrt);
1980         }
1981     }
1982 
1983     @Override
1984     protected Object visit(final ASTSizeFunction node, final Object data) {
1985         try {
1986             final Object val = node.jjtGetChild(0).jjtAccept(this, data);
1987             return operators.size(node, val);
1988         } catch (final JexlException xany) {
1989             return 0;
1990         }
1991     }
1992 
1993     @Override
1994     protected Object visit(final ASTStringLiteral node, final Object data) {
1995         if (data != null) {
1996             return getAttribute(data, node.getLiteral(), node);
1997         }
1998         return node.getLiteral();
1999     }
2000 
2001     @Override
2002     protected Object visit(final ASTSubNode node, final Object data) {
2003         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2004         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2005         try {
2006             final Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, left, right);
2007             return result != JexlEngine.TRY_FAILED ? result : arithmetic.subtract(left, right);
2008         } catch (final ArithmeticException xrt) {
2009             throw new JexlException(findNullOperand(node, left, right), "- error", xrt);
2010         }
2011     }
2012 
2013     @Override
2014     protected Object visit(final ASTSWNode node, final Object data) {
2015         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2016         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2017         return operators.startsWith(node, JexlOperator.STARTSWITH, left, right);
2018     }
2019 
2020 
2021     @Override
2022     protected Object visit(final ASTSwitchExpression node, final Object data) {
2023         final Object value = node.jjtGetChild(0).jjtAccept(this, data);
2024         final int index = node.switchIndex(value);
2025         if (index >= 0) {
2026           return node.jjtGetChild(index).jjtAccept(this, data);
2027         }
2028         if (isStrictEngine()) {
2029           throw new JexlException(node, "no case in switch expression for: " + value);
2030         }
2031         return null;
2032     }
2033 
2034     @Override
2035     protected Object visit(final ASTSwitchStatement node, final Object data) {
2036         final int count = node.jjtGetNumChildren();
2037         Object value = node.jjtGetChild(0).jjtAccept(this, data);
2038         final int index = node.switchIndex(value);
2039         if (index > 0) {
2040             if (!node.isStatement()) {
2041                 return node.jjtGetChild(index).jjtAccept(this, data);
2042             }
2043             for (int i = index; i < count; ++i) {
2044                 try {
2045                     // evaluate the switch body
2046                     value = node.jjtGetChild(i).jjtAccept(this, data);
2047                 } catch (final JexlException.Break xbreak) {
2048                    break; // break out of the switch
2049                 } catch (final JexlException.Continue xcontinue) {
2050                     // continue to next case
2051                 }
2052             }
2053             return value;
2054         }
2055         if (isStrictEngine()) {
2056             throw new JexlException(node, "no case in switch statement for: " + value);
2057         }
2058         return null;
2059     }
2060 
2061     @Override
2062     protected Object visit(final ASTTernaryNode node, final Object data) {
2063         Object condition;
2064         try {
2065             condition = node.jjtGetChild(0).jjtAccept(this, data);
2066         } catch (final JexlException xany) {
2067             if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
2068                 throw xany;
2069             }
2070             condition = null;
2071         }
2072         // ternary as in "x ? y : z"
2073         if (node.jjtGetNumChildren() == 3) {
2074             if (condition != null && arithmetic.testPredicate(condition)) {
2075                 return node.jjtGetChild(1).jjtAccept(this, data);
2076             }
2077             return node.jjtGetChild(2).jjtAccept(this, data);
2078         }
2079         // elvis as in "x ?: z"
2080         if (condition != null && arithmetic.testPredicate(condition)) {
2081             return condition;
2082         }
2083         return node.jjtGetChild(1).jjtAccept(this, data);
2084     }
2085 
2086     @Override
2087     protected Object visit(final ASTThrowStatement node, final Object data) {
2088         final Object thrown = node.jjtGetChild(0).jjtAccept(this, data);
2089         throw new JexlException.Throw(node, thrown);
2090     }
2091 
2092     @Override
2093     protected Object visit(final ASTTrueNode node, final Object data) {
2094         return Boolean.TRUE;
2095     }
2096 
2097     @Override
2098     protected Object visit(final ASTTryResources node, final Object data) {
2099         final int bodyChild = node.jjtGetNumChildren() - 1;
2100         final JexlNode tryBody = node.jjtGetChild(bodyChild);
2101         final Queue<Object> tryResult = new ArrayDeque<>(bodyChild);
2102         final LexicalFrame locals;
2103         // sequence of var declarations with/without assignment
2104         if (node.getSymbolCount() > 0) {
2105             locals = new LexicalFrame(frame, block);
2106             block = locals;
2107         } else {
2108             locals = null;
2109         }
2110         try {
2111             for(int c = 0; c < bodyChild; ++c) {
2112                 final JexlNode tryResource = node.jjtGetChild(c);
2113                 final Object result = tryResource.jjtAccept(this, data);
2114                 if (result != null) {
2115                     tryResult.add(result);
2116                 }
2117             }
2118             // evaluate the body
2119             return tryBody.jjtAccept(this, data);
2120         } finally {
2121             closeIfSupported(tryResult);
2122             // restore lexical frame
2123             if (locals != null) {
2124                 block = block.pop();
2125             }
2126         }
2127     }
2128 
2129     @Override
2130     protected Object visit(final ASTTryStatement node, final Object data) {
2131         int nc = 0;
2132         final JexlNode tryBody = node.jjtGetChild(nc++);
2133         JexlException rethrow = null;
2134         JexlException flowControl = null;
2135         Object result = null;
2136         try {
2137             // evaluate the try
2138             result = tryBody.jjtAccept(this, data);
2139         } catch(JexlException.Return | JexlException.Cancel |
2140                 JexlException.Break | JexlException.Continue xflow) {
2141             // flow control exceptions do not trigger the catch clause
2142             flowControl = xflow;
2143         } catch(final JexlException xany) {
2144             rethrow = xany;
2145         }
2146         JexlException thrownByCatch = null;
2147         if (rethrow != null && node.hasCatchClause()) {
2148             final ASTReference catchVar = (ASTReference) node.jjtGetChild(nc++);
2149             final JexlNode catchBody = node.jjtGetChild(nc++);
2150             // if we caught an exception and have a catch body, evaluate it
2151             try {
2152                 // evaluate the catch
2153                 result = evalCatch(catchVar, catchBody, rethrow, data);
2154                 // if catch body evaluates, do not rethrow
2155                 rethrow = null;
2156             } catch (JexlException.Return | JexlException.Cancel |
2157                      JexlException.Break | JexlException.Continue alterFlow) {
2158                 flowControl = alterFlow;
2159             } catch (final JexlException exception) {
2160                 // catching an exception thrown from catch body; can be a (re)throw
2161                 rethrow = thrownByCatch = exception;
2162             }
2163         }
2164         // if we have a 'finally' block, no matter what, evaluate it: its control flow will
2165         // take precedence over what the 'catch' block might have thrown.
2166         if (node.hasFinallyClause()) {
2167             final JexlNode finallyBody = node.jjtGetChild(nc);
2168             try {
2169                 finallyBody.jjtAccept(this, data);
2170             } catch (JexlException.Break | JexlException.Continue | JexlException.Return flowException) {
2171                 // potentially swallow previous, even return but not cancel
2172                 if (!(flowControl instanceof JexlException.Cancel)) {
2173                     flowControl = flowException;
2174                 }
2175             } catch (final JexlException.Cancel cancelException) {
2176                 // cancel swallows everything
2177                 flowControl = cancelException;
2178             } catch (final JexlException exception) {
2179                 // catching an exception thrown in finally body
2180                 if (jexl.logger.isDebugEnabled()) {
2181                     jexl.logger.debug("exception thrown in finally", exception);
2182                 }
2183                 // swallow the caught one
2184                 rethrow = exception;
2185             }
2186         }
2187         if (flowControl != null) {
2188             if (thrownByCatch != null && jexl.logger.isDebugEnabled()) {
2189                 jexl.logger.debug("finally swallowed exception thrown by catch", thrownByCatch);
2190             }
2191             throw flowControl;
2192         }
2193         if (rethrow != null) {
2194             throw rethrow;
2195         }
2196         return result;
2197     }
2198 
2199     @Override
2200     protected Object visit(final ASTUnaryMinusNode node, final Object data) {
2201         // use cached value if literal
2202         final Object value = node.jjtGetValue();
2203         if (value instanceof Number) {
2204             return value;
2205         }
2206         final JexlNode valNode = node.jjtGetChild(0);
2207         final Object val = valNode.jjtAccept(this, data);
2208         try {
2209             final Object result = operators.tryOverload(node, JexlOperator.NEGATE, val);
2210             if (result != JexlEngine.TRY_FAILED) {
2211                 return result;
2212             }
2213             Object number = arithmetic.negate(val);
2214             // attempt to recoerce to literal class
2215             // cache if number literal and negate is idempotent
2216             if (number instanceof Number && valNode instanceof ASTNumberLiteral) {
2217                 number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
2218                 if (arithmetic.isNegateStable()) {
2219                     node.jjtSetValue(number);
2220                 }
2221             }
2222             return number;
2223         } catch (final ArithmeticException xrt) {
2224             throw new JexlException(valNode, "- error", xrt);
2225         }
2226     }
2227 
2228     @Override
2229     protected Object visit(final ASTUnaryPlusNode node, final Object data) {
2230         // use cached value if literal
2231         final Object value = node.jjtGetValue();
2232         if (value instanceof Number) {
2233             return value;
2234         }
2235         final JexlNode valNode = node.jjtGetChild(0);
2236         final Object val = valNode.jjtAccept(this, data);
2237         try {
2238             final Object result = operators.tryOverload(node, JexlOperator.POSITIVIZE, val);
2239             if (result != JexlEngine.TRY_FAILED) {
2240                 return result;
2241             }
2242             final Object number = arithmetic.positivize(val);
2243             if (valNode instanceof ASTNumberLiteral
2244                 && number instanceof Number
2245                 && arithmetic.isPositivizeStable()) {
2246                 node.jjtSetValue(number);
2247             }
2248             return number;
2249         } catch (final ArithmeticException xrt) {
2250             throw new JexlException(valNode, "+ error", xrt);
2251         }
2252     }
2253 
2254     @Override
2255     protected Object visit(final ASTVar node, final Object data) {
2256         final int symbol = node.getSymbol();
2257         // if we have a var, we have a scope thus a frame
2258         if (!options.isLexical() && !node.isLexical()) {
2259             if (frame.has(symbol)) {
2260                 return frame.get(symbol);
2261             }
2262         } else if (!defineVariable(node, block)) {
2263             return redefinedVariable(node, node.getName());
2264         }
2265         frame.set(symbol, null);
2266         return null;
2267     }
2268 
2269     @Override
2270     protected Object visit(final ASTWhileStatement node, final Object data) {
2271         Object result = null;
2272         /* first objectNode is the condition */
2273         final JexlNode condition = node.jjtGetChild(0);
2274         while (testPredicate(condition, condition.jjtAccept(this, data))) {
2275             cancelCheck(node);
2276             if (node.jjtGetNumChildren() > 1) {
2277                 try {
2278                     // execute statement
2279                     result = node.jjtGetChild(1).jjtAccept(this, data);
2280                 } catch (final JexlException.Break stmtBreak) {
2281                     break;
2282                 } catch (final JexlException.Continue stmtContinue) {
2283                     //continue;
2284                 }
2285             }
2286         }
2287         return result;
2288     }
2289 
2290     /**
2291      * Base visitation for blocks.
2292      * @param node the block
2293      * @param data the usual data
2294      * @return the result of the last expression evaluation
2295      */
2296     private Object visitBlock(final ASTBlock node, final Object data) {
2297         final int numChildren = node.jjtGetNumChildren();
2298         Object result = null;
2299         for (int i = 0; i < numChildren; i++) {
2300             cancelCheck(node);
2301             result = node.jjtGetChild(i).jjtAccept(this, data);
2302         }
2303         return result;
2304     }
2305 
2306     /**
2307      * Runs a node.
2308      * @param node the node
2309      * @param data the usual data, always null
2310      * @return the return value
2311      */
2312     protected Object visitLexicalNode(final JexlNode node, final Object data) {
2313         block = new LexicalFrame(frame, null);
2314         try {
2315             return node.jjtAccept(this, data);
2316         } finally {
2317             block = block.pop();
2318         }
2319     }
2320 }