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