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