This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git

commit 20af879ca618361b311db8eae1079eb205920334
Author: henrib <hen...@apache.org>
AuthorDate: Wed Aug 8 18:45:45 2018 +0200

    JEXL: cleansing and refactoring the Interpreter.call(...) that was way too 
convoluted
---
 .../apache/commons/jexl3/internal/Debugger.java    |  14 +-
 .../apache/commons/jexl3/internal/Interpreter.java | 340 ++++++---------------
 .../commons/jexl3/internal/InterpreterBase.java    | 301 ++++++++++++++++++
 .../commons/jexl3/internal/TemplateDebugger.java   |   8 +
 4 files changed, 409 insertions(+), 254 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java 
b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
index db75d81..c3203e3 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -127,7 +127,19 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     public Debugger() {
     }
-
+    
+    /**
+     * Resets this debugger state.
+     */
+    public void reset() {
+        builder.setLength(0);
+        cause = null;
+        start = 0;
+        end = 0;
+        indentLevel = 0;
+        indent = 2;
+    }
+    
     /**
      * Position the debugger on the root of an expression.
      * @param jscript the expression
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java 
b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 0ff4796..46d692b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -1376,156 +1376,6 @@ public class Interpreter extends InterpreterBase {
     }
 
     /**
-     * Concatenate arguments in call(...).
-     * <p>When target == context, we are dealing with a global namespace 
function call
-     * @param target the pseudo-method owner, first to-be argument
-     * @param narrow whether we should attempt to narrow number arguments
-     * @param args   the other (non null) arguments
-     * @return the arguments array
-     */
-    private Object[] functionArguments(Object target, boolean narrow, Object[] 
args) {
-        // when target == context, we are dealing with the null namespace
-        if (target == null || target == context) {
-            if (narrow) {
-                arithmetic.narrowArguments(args);
-            }
-            return args;
-        }
-        // makes target 1st args, copy others - optionally narrow numbers
-        Object[] nargv = new Object[args.length + 1];
-        if (narrow) {
-            nargv[0] = functionArgument(true, target);
-            for (int a = 1; a <= args.length; ++a) {
-                nargv[a] = functionArgument(true, args[a - 1]);
-            }
-        } else {
-            nargv[0] = target;
-            System.arraycopy(args, 0, nargv, 1, args.length);
-        }
-        return nargv;
-    }
-
-    /**
-     * Concatenate arguments in call(...).
-     * @param target the pseudo-method owner, first to-be argument
-     * @param narrow whether we should attempt to narrow number arguments
-     * @param args   the other (non null) arguments
-     * @return the arguments array
-     */
-    private Object[] callArguments(Object target, boolean narrow, Object[] 
args) {
-        // makes target 1st args, copy others - optionally narrow numbers
-        Object[] nargv = new Object[args.length + 1];
-        if (narrow) {
-            nargv[0] = functionArgument(true, target);
-            for (int a = 1; a <= args.length; ++a) {
-                nargv[a] = functionArgument(true, args[a - 1]);
-            }
-        } else {
-            nargv[0] = target;
-            System.arraycopy(args, 0, nargv, 1, args.length);
-        }
-        return nargv;
-    }
-    
-    /**
-     * Optionally narrows an argument for a function call.
-     * @param narrow whether narrowing should occur
-     * @param arg    the argument
-     * @return the narrowed argument
-     */
-    private Object functionArgument(boolean narrow, Object arg) {
-        return narrow && arg instanceof Number ? arithmetic.narrow((Number) 
arg) : arg;
-    }
-
-    /**
-     * Cached function call.
-     */
-    private static class Funcall implements JexlNode.Funcall {
-        /** Whether narrow should be applied to arguments. */
-        protected final boolean narrow;
-        /** The JexlMethod to delegate the call to. */
-        protected final JexlMethod me;
-        /**
-         * Constructor.
-         * @param jme  the method
-         * @param flag the narrow flag
-         */
-        protected Funcall(JexlMethod jme, boolean flag) {
-            this.me = jme;
-            this.narrow = flag;
-        }
-
-        /**
-         * Try invocation.
-         * @param ii     the interpreter
-         * @param name   the method name
-         * @param target the method target
-         * @param args   the method arguments
-         * @return the method invocation result (or JexlEngine.TRY_FAILED)
-         */
-        protected Object tryInvoke(Interpreter ii, String name, Object target, 
Object[] args) {
-            return me.tryInvoke(name, target, ii.functionArguments(null, 
narrow, args));
-        }
-    }
-
-    /**
-     * Cached arithmetic function call.
-     */
-    private static class ArithmeticFuncall extends Funcall {
-        /**
-         * Constructor.
-         * @param jme  the method
-         * @param flag the narrow flag
-         */
-        protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
-            super(jme, flag);
-        }
-
-        @Override
-        protected Object tryInvoke(Interpreter ii, String name, Object target, 
Object[] args) {
-            return me.tryInvoke(name, ii.arithmetic, 
ii.functionArguments(target, narrow, args));
-        }
-    }
-
-    /**
-     * Cached context function call.
-     */
-    private static class ContextFuncall extends Funcall {
-        /**
-         * Constructor.
-         * @param jme  the method
-         * @param flag the narrow flag
-         */
-        protected ContextFuncall(JexlMethod jme, boolean flag) {
-            super(jme, flag);
-        }
-
-        @Override
-        protected Object tryInvoke(Interpreter ii, String name, Object target, 
Object[] args) {
-            return me.tryInvoke(name, ii.context, ii.functionArguments(target, 
narrow, args));
-        }
-    }
-    
-    /**
-     * A ctor that needs a context as 1st argument.
-     */
-    private static class ContextualCtor extends Funcall {
-        /**
-         * Constructor.
-         * @param jme the method
-         * @param flag the narrow flag
-         */
-        protected ContextualCtor(JexlMethod jme, boolean flag) {
-            super(jme, flag);
-        }
-
-        @Override
-        protected Object tryInvoke(Interpreter ii, String name, Object target, 
Object[] args) {
-            return me.tryInvoke(name, target, ii.callArguments(ii.context, 
narrow, args));
-        }
-    }
-    
-    /**
      * Calls a method (or function).
      * <p>
      * Method resolution is a follows:
@@ -1545,99 +1395,99 @@ public class Interpreter extends InterpreterBase {
     protected Object call(final JexlNode node, Object target, Object functor, 
final ASTArguments argNode) {
         cancelCheck(node);
         // evaluate the arguments
-        Object[] argv = visit(argNode, null);
+        final Object[] argv = visit(argNode, null);
         // get the method name if identifier
         final int symbol;
         final String methodName;
+        boolean cacheable = cache;
+        boolean isavar = false;
         if (functor instanceof ASTIdentifier) {
+            // function call, target is context or namespace (if there was one)
             ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
             symbol = methodIdentifier.getSymbol();
             methodName = methodIdentifier.getName();
             functor = null;
+            // is it a global or local variable ?
+            if (target == context) {
+                if (symbol >= 0) {
+                    functor = frame.get(symbol);
+                    isavar = functor != null;
+                } else if (context.has(methodName)) {
+                    functor = context.get(methodName);
+                    isavar = functor != null;
+                } 
+                // name is a variable, cant be cached
+                cacheable &= !isavar;
+            }
         } else if (functor instanceof ASTIdentifierAccess) {
+            // a method call on target
             methodName = ((ASTIdentifierAccess) functor).getName();
             symbol = -1;
             functor = null;
+            cacheable = true;
         } else if (functor != null) {
+            // ...(x)(y)
             symbol = -1 - 1; // -2;
             methodName = null;
+            cacheable = false;
         } else {
             return unsolvableMethod(node, "?");
         }
-        // at this point, either the functor is a non null (hopefully) 
'invocable' object or we do have the methodName
-        Object caller = target;
+        
+        // solving the call site
+        CallDispatcher call = new CallDispatcher(node, cacheable);
         try {
-            boolean cacheable = cache;
-            // do we have  a method/function name ?
-            if (methodName != null) {
-                // is it a global or local variable ?
-                if (target == context) {
-                    boolean isavar = true;
-                    if (symbol >= 0) {
-                        functor = frame.get(symbol);
-                    } else if (context.has(methodName)) {
-                        functor = context.get(methodName);
-                    } else {
-                        isavar = false;
-                    }
-                    // name is a variable, must be a functor, cant be cached
-                    if (isavar) {
-                        if (functor == null) {
-                            return unsolvableMethod(node, methodName);
-                        }
-                        cacheable = false;
-                    }
-                }
-                // attempt to reuse last funcall cached in volatile 
JexlNode.value (if it was not a variable)
-                if (cacheable) {
-                    Object cached = node.jjtGetValue();
-                    if (cached instanceof Funcall) {
-                        Object eval = ((Funcall) cached).tryInvoke(this, 
methodName, target, argv);
-                        if (JexlEngine.TRY_FAILED != eval) {
-                            return eval;
-                        }
-                    }
-                }
-            } else {
-                // if no name, we should not cache
-                cacheable = false;
-            }
+            // do we have a  cached version method/function name ?
+            Object eval = call.tryEval(target, methodName, argv);
+            if (JexlEngine.TRY_FAILED != eval) {
+                return eval;
+            } 
+            boolean functorp = false;
             boolean narrow = false;
-            JexlMethod vm = null;
-            Funcall funcall = null;
-            // pseudo loop and a half to try acquiring methods without and 
with argument narrowing
+            // pseudo loop to try acquiring methods without and with argument 
narrowing
             while (true) {
-                if (functor == null) {
-                    // try a method
-                    vm = uberspect.getMethod(target, methodName, argv);
-                    if (vm != null) {
-                        if (cacheable && vm.isCacheable()) {
-                            funcall = new Funcall(vm, narrow);
-                        }
-                        break;
+                call.narrow = narrow;
+                // direct function or method call
+                if (functor == null || functorp) {
+                    // try a method or function from context
+                    if (call.isTargetMethod(target, methodName, argv)) {
+                        return call.eval(methodName);
                     }
-                    // solve 'null' namespace
                     if (target == context) {
+                        // solve 'null' namespace
                         Object namespace = resolveNamespace(null, node);
-                        if (namespace == context) {
-                            // we can not solve it
-                            break;
-                        } else if (namespace != null) {
-                            target = namespace;
-                            caller = null;
-                            continue;
+                        if (namespace != null
+                            && namespace != context
+                            && call.isTargetMethod(namespace, methodName, 
argv)) {
+                            return call.eval(methodName);
+                        }
+                        // do not try context function since this was attempted
+                        // 10 lines above...; solve as an arithmetic function
+                        if (call.isArithmeticMethod(methodName, argv)) {
+                            return call.eval(methodName);
                         }
                         // could not find a method, try as a property of a 
non-context target (performed once)
-                    } else if (!narrow) {
-                        // the method may be a functor stored in a property of 
the target
-                        JexlPropertyGet get = uberspect.getPropertyGet(target, 
methodName);
-                        if (get != null) {
-                            functor = get.tryInvoke(target, methodName);
+                    } else {
+                        // try prepending target to arguments and look for
+                        // applicable method in context...
+                        Object[] pargv = functionArguments(target, narrow, 
argv);
+                        if (call.isContextMethod(methodName, pargv)) {
+                            return call.eval(methodName);
+                        }
+                        // ...or arithmetic
+                        if (call.isArithmeticMethod(methodName, pargv)) {
+                            return call.eval(methodName);
+                        }
+                        // the method may also be a functor stored in a 
property of the target
+                        if (!narrow) {
+                            JexlPropertyGet get = 
uberspect.getPropertyGet(target, methodName);
+                            if (get != null) {
+                                functor = get.tryInvoke(target, methodName);
+                                functorp = functor != null;
+                            }
                         }
                     }
                 }
-                final Object[] nargv;
-                final String mname;
                 // this may happen without the above when we are chaining call 
like x(a)(b)
                 // or when a var/symbol or antish var is used as a "function" 
name
                 if (functor != null) {
@@ -1648,56 +1498,40 @@ public class Interpreter extends InterpreterBase {
                     if (functor instanceof JexlMethod) {
                         return ((JexlMethod) functor).invoke(target, argv);
                     }
-                    // a generic callable
-                    mname = "call";
-                    vm = uberspect.getMethod(functor, mname, argv);
-                    if (vm != null) {
-                        return vm.invoke(functor, argv);
+                    final String mCALL = "call";
+                    // may be a generic callable, try a 'call' method
+                    if (call.isTargetMethod(functor, mCALL, argv)) {
+                        return call.eval(mCALL);
                     }
-                    // prepend functor to arg to try JexlArithmetic or 
JexlContext function
-                    nargv = functionArguments(functor, narrow, argv);
-                } else {
-                    mname = methodName;
-                    // no need to narrow since this has been performed in 
previous loop
-                    nargv = functionArguments(caller, narrow, argv);
-                }
-                vm = uberspect.getMethod(context, mname, nargv);
-                if (vm != null) {
-                    argv = nargv;
-                    target = context;
-                    if (cacheable && vm.isCacheable()) {
-                        funcall = new ContextFuncall(vm, narrow);
+                    // functor is a var, may be method is a global one ?
+                    if (isavar && target == context) {
+                        if (call.isContextMethod(methodName, argv)) {
+                            return call.eval(methodName);
+                        }
+                        if (call.isArithmeticMethod(methodName, argv)) {
+                            return call.eval(methodName);
+                        }
                     }
-                    break;
-                }
-                vm = uberspect.getMethod(arithmetic, mname, nargv);
-                if (vm != null) {
-                    argv = nargv;
-                    target = arithmetic;
-                    if (cacheable && vm.isCacheable()) {
-                        funcall = new ArithmeticFuncall(vm, narrow);
+                    // try prepending functor to arguments and look for
+                    // context or arithmetic function called 'call'
+                    Object[] pargv = functionArguments(functor, narrow, argv);
+                    if (call.isContextMethod(mCALL, pargv)) {
+                        return call.eval(mCALL);
+                    }
+                    if (call.isArithmeticMethod(mCALL, pargv)) {
+                        return call.eval(mCALL);
                     }
-                    break;
                 }
                 // if we did not find an exact method by name and we haven't 
tried yet,
                 // attempt to narrow the parameters and if this succeeds, try 
again in next loop
                 if (!narrow && arithmetic.narrowArguments(argv)) {
                     narrow = true;
                     continue;
+                } else {
+                    break;
                 }
-                // stop trying
-                break;
-            }
-            // we have either evaluated and returned or might have found a 
method
-            if (vm != null) {
-                // vm cannot be null if xjexl is null
-                Object eval = vm.invoke(target, argv);
-                // cache executor in volatile JexlNode.value
-                if (funcall != null) {
-                    node.jjtSetValue(funcall);
-                }
-                return eval;
             }
+            // we have either evaluated and returned or no method was found
             return unsolvableMethod(node, methodName);
         } catch (JexlException xthru) {
             throw xthru;
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java 
b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 9f77987..865ba7e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -299,4 +299,305 @@ public abstract class InterpreterBase extends 
ParserVisitor {
             throw new JexlException.Cancel(node);
         }
     }
+    
+    /**
+     * Concatenate arguments in call(...).
+     * <p>When target == context, we are dealing with a global namespace 
function call
+     * @param target the pseudo-method owner, first to-be argument
+     * @param narrow whether we should attempt to narrow number arguments
+     * @param args   the other (non null) arguments
+     * @return the arguments array
+     */
+    protected Object[] functionArguments(Object target, boolean narrow, 
Object[] args) {
+        // when target == context, we are dealing with the null namespace
+        if (target == null || target == context) {
+            if (narrow) {
+                arithmetic.narrowArguments(args);
+            }
+            return args;
+        }
+        // makes target 1st args, copy others - optionally narrow numbers
+        Object[] nargv = new Object[args.length + 1];
+        if (narrow) {
+            nargv[0] = functionArgument(true, target);
+            for (int a = 1; a <= args.length; ++a) {
+                nargv[a] = functionArgument(true, args[a - 1]);
+            }
+        } else {
+            nargv[0] = target;
+            System.arraycopy(args, 0, nargv, 1, args.length);
+        }
+        return nargv;
+    }
+
+    /**
+     * Concatenate arguments in call(...).
+     * @param target the pseudo-method owner, first to-be argument
+     * @param narrow whether we should attempt to narrow number arguments
+     * @param args   the other (non null) arguments
+     * @return the arguments array
+     */
+    protected Object[] callArguments(Object target, boolean narrow, Object[] 
args) {
+        // makes target 1st args, copy others - optionally narrow numbers
+        Object[] nargv = new Object[args.length + 1];
+        if (narrow) {
+            nargv[0] = functionArgument(true, target);
+            for (int a = 1; a <= args.length; ++a) {
+                nargv[a] = functionArgument(true, args[a - 1]);
+            }
+        } else {
+            nargv[0] = target;
+            System.arraycopy(args, 0, nargv, 1, args.length);
+        }
+        return nargv;
+    }
+    
+    /**
+     * Optionally narrows an argument for a function call.
+     * @param narrow whether narrowing should occur
+     * @param arg    the argument
+     * @return the narrowed argument
+     */
+    protected Object functionArgument(boolean narrow, Object arg) {
+        return narrow && arg instanceof Number ? arithmetic.narrow((Number) 
arg) : arg;
+    }
+
+    /**
+     * Cached function call.
+     */
+    protected static class Funcall implements JexlNode.Funcall {
+        /** Whether narrow should be applied to arguments. */
+        protected final boolean narrow;
+        /** The JexlMethod to delegate the call to. */
+        protected final JexlMethod me;
+        /**
+         * Constructor.
+         * @param jme  the method
+         * @param flag the narrow flag
+         */
+        protected Funcall(JexlMethod jme, boolean flag) {
+            this.me = jme;
+            this.narrow = flag;
+        }
+
+        /**
+         * Try invocation.
+         * @param ii     the interpreter
+         * @param name   the method name
+         * @param target the method target
+         * @param args   the method arguments
+         * @return the method invocation result (or JexlEngine.TRY_FAILED)
+         */
+        protected Object tryInvoke(InterpreterBase ii, String name, Object 
target, Object[] args) {
+            return me.tryInvoke(name, target, ii.functionArguments(null, 
narrow, args));
+        }
+    }
+
+    /**
+     * Cached arithmetic function call.
+     */
+    protected static class ArithmeticFuncall extends Funcall {
+        /**
+         * Constructor.
+         * @param jme  the method
+         * @param flag the narrow flag
+         */
+        protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
+
+        @Override
+        protected Object tryInvoke(InterpreterBase ii, String name, Object 
target, Object[] args) {
+            return me.tryInvoke(name, ii.arithmetic, 
ii.functionArguments(target, narrow, args));
+        }
+    }
+
+    /**
+     * Cached context function call.
+     */
+    protected static class ContextFuncall extends Funcall {
+        /**
+         * Constructor.
+         * @param jme  the method
+         * @param flag the narrow flag
+         */
+        protected ContextFuncall(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
+
+        @Override
+        protected Object tryInvoke(InterpreterBase ii, String name, Object 
target, Object[] args) {
+            return me.tryInvoke(name, ii.context, ii.functionArguments(target, 
narrow, args));
+        }
+    }
+    
+    /**
+     * A ctor that needs a context as 1st argument.
+     */
+    protected static class ContextualCtor extends Funcall {
+        /**
+         * Constructor.
+         * @param jme the method
+         * @param flag the narrow flag
+         */
+        protected ContextualCtor(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
+
+        @Override
+        protected Object tryInvoke(InterpreterBase ii, String name, Object 
target, Object[] args) {
+            return me.tryInvoke(name, target, ii.callArguments(ii.context, 
narrow, args));
+        }
+    }
+    
+    /**
+     * Helping dispatch function calls.
+     */
+    protected class CallDispatcher {
+        /**
+         * The syntactic node.
+         */
+        final JexlNode node;
+        /**
+         * Whether solution is cacheable.
+         */
+        boolean cacheable = true;
+        /**
+         * Whether arguments have been narrowed.
+         */
+        boolean narrow = false;
+        /**
+         * The method to call.
+         */
+        JexlMethod vm = null;
+        /**
+         * The method invocation target.
+         */
+        Object target = null;
+        /**
+         * The actual arguments.
+         */
+        Object[] argv = null;
+        /**
+         * The cacheable funcall if any.
+         */
+        Funcall funcall = null;
+
+        /**
+         * Dispatcher ctor.
+         *
+         * @param anode the syntactic node.
+         * @param acacheable whether resolution can be cached
+         */
+        CallDispatcher(JexlNode anode, boolean acacheable) {
+            this.node = anode;
+            this.cacheable = acacheable;
+        }
+
+        /**
+         * Whether the method is a target method.
+         *
+         * @param ntarget the target instance
+         * @param mname the method name
+         * @param arguments the method arguments
+         * @return true if arithmetic, false otherwise
+         */
+        protected boolean isTargetMethod(Object ntarget, String mname, final 
Object[] arguments) {
+            // try a method
+            vm = uberspect.getMethod(ntarget, mname, arguments);
+            if (vm != null) {
+                argv = arguments;
+                target = ntarget;
+                if (cacheable && vm.isCacheable()) {
+                    funcall = new Funcall(vm, narrow);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Whether the method is a context method.
+         *
+         * @param mname the method name
+         * @param arguments the method arguments
+         * @return true if arithmetic, false otherwise
+         */
+        protected boolean isContextMethod(String mname, final Object[] 
arguments) {
+            vm = uberspect.getMethod(context, mname, arguments);
+            if (vm != null) {
+                argv = arguments;
+                target = context;
+                if (cacheable && vm.isCacheable()) {
+                    funcall = new ContextFuncall(vm, narrow);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Whether the method is an arithmetic method.
+         *
+         * @param mname the method name
+         * @param arguments the method arguments
+         * @return true if arithmetic, false otherwise
+         */
+        protected boolean isArithmeticMethod(String mname, final Object[] 
arguments) {
+            vm = uberspect.getMethod(arithmetic, mname, arguments);
+            if (vm != null) {
+                argv = arguments;
+                target = arithmetic;
+                if (cacheable && vm.isCacheable()) {
+                    funcall = new ArithmeticFuncall(vm, narrow);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Attempt to reuse last funcall cached in volatile JexlNode.value (if
+         * it was cacheable).
+         *
+         * @param ntarget the target instance
+         * @param mname the method name
+         * @param arguments the method arguments
+         * @return TRY_FAILED if invocation was not possible or failed, the
+         * result otherwise
+         */
+        protected Object tryEval(final Object ntarget, final String mname, 
final Object[] arguments) {
+            // do we have  a method/function name ?
+            // attempt to reuse last funcall cached in volatile JexlNode.value 
(if it was not a variable)
+            if (mname != null && cacheable && ntarget != null) {
+                Object cached = node.jjtGetValue();
+                if (cached instanceof Funcall) {
+                    return ((Funcall) cached).tryInvoke(InterpreterBase.this, 
mname, ntarget, arguments);
+                }
+            }
+            return JexlEngine.TRY_FAILED;
+        }
+
+        /**
+         * Evaluates the method previously dispatched.
+         *
+         * @param mname the method name
+         * @return the method invocation result
+         * @throws Exception when invocation fails
+         */
+        protected Object eval(String mname) throws Exception {
+            // we have either evaluated and returned or might have found a 
method
+            if (vm != null) {
+                // vm cannot be null if xjexl is null
+                Object eval = vm.invoke(target, argv);
+                // cache executor in volatile JexlNode.value
+                if (funcall != null) {
+                    node.jjtSetValue(funcall);
+                }
+                return eval;
+            }
+            return unsolvableMethod(node, mname);
+        }
+    }
+
 }
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java 
b/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
index 394d9b9..575d713 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateDebugger.java
@@ -47,6 +47,14 @@ public class TemplateDebugger extends Debugger {
     public TemplateDebugger() {
     }
 
+    @Override
+    public void reset() {
+        super.reset();
+        // so we can use it more than one time
+        exprs = null;
+        script = null;
+    }
+    
     /**
      * Position the debugger on the root of a template expression.
      * @param je the expression

Reply via email to