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