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


The following commit(s) were added to refs/heads/master by this push:
     new ef73c99a JEXL-372, JEXL-373: fixing multiple vars declaration in loop, 
grammar change; refactored postfix/prefix operators; more tests;
ef73c99a is described below

commit ef73c99ad317ed20328a1297dc7b30a89f9a8d13
Author: henrib <[email protected]>
AuthorDate: Fri Jul 1 15:29:11 2022 +0200

    JEXL-372, JEXL-373: fixing multiple vars declaration in loop, grammar 
change; refactored postfix/prefix operators; more tests;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  33 ++--
 .../org/apache/commons/jexl3/JexlOperator.java     |  91 +++++----
 .../apache/commons/jexl3/internal/Debugger.java    |  96 +++++++--
 .../org/apache/commons/jexl3/internal/Engine.java  |   2 +-
 .../apache/commons/jexl3/internal/Interpreter.java | 135 +++++++------
 .../apache/commons/jexl3/internal/Operators.java   | 162 +++++++++------
 .../org/apache/commons/jexl3/internal/Scope.java   |  24 +--
 .../commons/jexl3/internal/ScriptVisitor.java      |   7 +-
 .../apache/commons/jexl3/parser/JexlParser.java    |   7 +-
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  14 +-
 .../apache/commons/jexl3/parser/ParserVisitor.java |  12 +-
 .../org/apache/commons/jexl3/ArithmeticTest.java   | 139 +++++++++++++
 .../java/org/apache/commons/jexl3/ForEachTest.java |  27 +++
 .../org/apache/commons/jexl3/JexlTestCase.java     |   7 +
 .../java/org/apache/commons/jexl3/LexicalTest.java |   2 +-
 .../org/apache/commons/jexl3/SideEffectTest.java   | 217 +++++++++++++++++----
 .../org/apache/commons/jexl3/junit/Asserter.java   |  14 +-
 17 files changed, 729 insertions(+), 260 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 1ca2bd42..4610d227 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -534,7 +534,7 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a Number, return back the value attempting to narrow it to a 
target class.
+     * Given a Number, return the value attempting to narrow it to a target 
class.
      *
      * @param original the original number
      * @param narrow   the attempted target class
@@ -613,14 +613,14 @@ public class JexlArithmetic {
      * if either arguments is a Long, no narrowing to Integer will occur
      * </p>
      *
-     * @param lhs  the left hand side operand that lead to the bigi result
-     * @param rhs  the right hand side operand that lead to the bigi result
+     * @param lhs  the left-hand side operand that lead to the bigi result
+     * @param rhs  the right-hand side operand that lead to the bigi result
      * @param bigi the BigInteger to narrow
      * @return an Integer or Long if narrowing is possible, the original 
BigInteger otherwise
      */
     protected Number narrowBigInteger(final Object lhs, final Object rhs, 
final BigInteger bigi) {
         //coerce to long if possible
-        if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
+        if ((isNumberable(lhs) || isNumberable(rhs))
                 && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
                 && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
             // coerce to int if possible
@@ -637,13 +637,13 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a BigDecimal, attempt to narrow it to an Integer or Long if it 
fits if
+     * Given a BigDecimal, attempt to narrow it to an Integer or Long if it 
fits and
      * one of the arguments is a numberable.
      *
-     * @param lhs  the left hand side operand that lead to the bigd result
-     * @param rhs  the right hand side operand that lead to the bigd result
+     * @param lhs  the left-hand side operand that lead to the bigd result
+     * @param rhs  the right-hand side operand that lead to the bigd result
      * @param bigd the BigDecimal to narrow
-     * @return an Integer or Long if narrowing is possible, the original 
BigInteger otherwise
+     * @return an Integer or Long if narrowing is possible, the original 
BigDecimal otherwise
      */
     protected Number narrowBigDecimal(final Object lhs, final Object rhs, 
final BigDecimal bigd) {
         if (isNumberable(lhs) || isNumberable(rhs)) {
@@ -755,19 +755,21 @@ public class JexlArithmetic {
             return ((Long) val) + incr;
         }
         if (val instanceof BigDecimal) {
-            return ((BigDecimal) val).add(BigDecimal.valueOf(incr));
+            BigDecimal bd = (BigDecimal) val;
+            return bd.add(BigDecimal.valueOf(incr), this.mathContext);
         }
         if (val instanceof BigInteger) {
-            return ((BigInteger) val).add(BigInteger.valueOf(incr));
+            BigInteger bi = (BigInteger) val;
+            return bi.add(BigInteger.valueOf(incr));
         }
         if (val instanceof Float) {
             return ((Float) val) + incr;
         }
         if (val instanceof Short) {
-            return (short) ((Short) val) + incr;
+            return (short) (((Short) val) + incr);
         }
         if (val instanceof Byte) {
-            return (byte) ((Byte) val) + incr;
+            return (byte) (((Byte) val) + incr);
         }
         throw new ArithmeticException("Object "+(incr < 0? 
"decrement":"increment")+":(" + val + ")");
     }
@@ -1046,6 +1048,7 @@ public class JexlArithmetic {
     /**
      * Negates a value (unary minus for numbers).
      *
+     * @see #isNegateStable()
      * @param val the value to negate
      * @return the negated value
      */
@@ -1092,7 +1095,6 @@ public class JexlArithmetic {
      * <p>This is used to determine whether negate results on number literals 
can be cached.
      * If the result on calling negate with the same constant argument may 
change between calls,
      * which means the function is not deterministic, this method must return 
false.
-     * @see #isNegateStable()
      * @return true if negate is idempotent, false otherwise
      */
     public boolean isNegateStable() {
@@ -1676,7 +1678,7 @@ public class JexlArithmetic {
      */
     private double parseDouble(String arg) throws ArithmeticException {
         try {
-            return arg.isEmpty()? Double.NaN : Double.parseDouble((String) 
arg);
+            return arg.isEmpty()? Double.NaN : Double.parseDouble(arg);
         } catch(NumberFormatException xformat) {
             throw new ArithmeticException("Double coercion: ("+ arg +")");
         }
@@ -1862,8 +1864,7 @@ public class JexlArithmetic {
             return parseDouble((String) val);
         }
         if (val instanceof Character) {
-            final int i = ((Character) val);
-            return i;
+            return ((Character) val);
         }
         throw new ArithmeticException("Double coercion: "
                 + val.getClass().getName() + ":(" + val + ")");
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java 
b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 820ad0d0..2b93eb24 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -28,7 +28,7 @@ package org.apache.commons.jexl3;
  * <p>The default JexlArithmetic implements generic versions of these methods 
using Object as arguments.
  * You can use your own derived JexlArithmetic that override and/or overload 
those operator methods.
  * Note that these are overloads by convention, not actual Java overloads.
- * The following rules apply to operator methods:</p>
+ * The following rules apply to all operator methods:</p>
  * <ul>
  * <li>Operator methods should be public</li>
  * <li>Operators return type should be respected when primitive (int, 
boolean,...)</li>
@@ -36,6 +36,14 @@ package org.apache.commons.jexl3;
  * <li>Operators may return JexlEngine.TRY_AGAIN to fallback on default JEXL 
implementation</li>
  * </ul>
  *
+ * For side effect operators, operators that modify the left-hand size value 
(+=, -=, etc), the user implemented
+ * overload methods may return:
+ * <ul>
+ *     <li>JexlEngine.TRY_FAIL to let the default fallback behavior be 
executed.</li>
+ *     <li>Any other value will be used as the new value to be assigned to the 
left-hand-side.</li>
+ * </ul>
+ * Note that side effect operators always return the left-hand side value 
(with an exception for postfix ++ and --).
+ *
  * @since 3.0
  */
 public enum JexlOperator {
@@ -44,7 +52,7 @@ public enum JexlOperator {
      * Add operator.
      * <br><strong>Syntax:</strong> <code>x + y</code>
      * <br><strong>Method:</strong> <code>T add(L x, R y);</code>.
-     * @see JexlArithmetic#add
+     * @see JexlArithmetic#add(Object, Object)
      */
     ADD("+", "add", 2),
 
@@ -52,7 +60,7 @@ public enum JexlOperator {
      * Subtract operator.
      * <br><strong>Syntax:</strong> <code>x - y</code>
      * <br><strong>Method:</strong> <code>T subtract(L x, R y);</code>.
-     * @see JexlArithmetic#subtract
+     * @see JexlArithmetic#subtract(Object, Object)
      */
     SUBTRACT("-", "subtract", 2),
 
@@ -60,7 +68,7 @@ public enum JexlOperator {
      * Multiply operator.
      * <br><strong>Syntax:</strong> <code>x * y</code>
      * <br><strong>Method:</strong> <code>T multiply(L x, R y);</code>.
-     * @see JexlArithmetic#multiply
+     * @see JexlArithmetic#multiply(Object, Object)
      */
     MULTIPLY("*", "multiply", 2),
 
@@ -68,7 +76,7 @@ public enum JexlOperator {
      * Divide operator.
      * <br><strong>Syntax:</strong> <code>x / y</code>
      * <br><strong>Method:</strong> <code>T divide(L x, R y);</code>.
-     * @see JexlArithmetic#divide
+     * @see JexlArithmetic#divide(Object, Object)
      */
     DIVIDE("/", "divide", 2),
 
@@ -76,7 +84,7 @@ public enum JexlOperator {
      * Modulo operator.
      * <br><strong>Syntax:</strong> <code>x % y</code>
      * <br><strong>Method:</strong> <code>T mod(L x, R y);</code>.
-     * @see JexlArithmetic#mod
+     * @see JexlArithmetic#mod(Object, Object)
      */
     MOD("%", "mod", 2),
 
@@ -84,7 +92,7 @@ public enum JexlOperator {
      * Bitwise-and operator.
      * <br><strong>Syntax:</strong> <code>x &amp; y</code>
      * <br><strong>Method:</strong> <code>T and(L x, R y);</code>.
-     * @see JexlArithmetic#and
+     * @see JexlArithmetic#and(Object, Object)
      */
     AND("&", "and", 2),
 
@@ -92,7 +100,7 @@ public enum JexlOperator {
      * Bitwise-or operator.
      * <br><strong>Syntax:</strong> <code>x | y</code>
      * <br><strong>Method:</strong> <code>T or(L x, R y);</code>.
-     * @see JexlArithmetic#or
+     * @see JexlArithmetic#or(Object, Object)
      */
     OR("|", "or", 2),
 
@@ -100,7 +108,7 @@ public enum JexlOperator {
      * Bitwise-xor operator.
      * <br><strong>Syntax:</strong> <code>x ^ y</code>
      * <br><strong>Method:</strong> <code>T xor(L x, R y);</code>.
-     * @see JexlArithmetic#xor
+     * @see JexlArithmetic#xor(Object, Object)
      */
     XOR("^", "xor", 2),
 
@@ -132,7 +140,7 @@ public enum JexlOperator {
      * Equals operator.
      * <br><strong>Syntax:</strong> <code>x == y</code>
      * <br><strong>Method:</strong> <code>boolean equals(L x, R y);</code>.
-     * @see JexlArithmetic#equals
+     * @see JexlArithmetic#equals(Object, Object)
      */
     EQ("==", "equals", 2),
 
@@ -140,7 +148,7 @@ public enum JexlOperator {
      * Less-than operator.
      * <br><strong>Syntax:</strong> <code>x &lt; y</code>
      * <br><strong>Method:</strong> <code>boolean lessThan(L x, R y);</code>.
-     * @see JexlArithmetic#lessThan
+     * @see JexlArithmetic#lessThan(Object, Object)
      */
     LT("<", "lessThan", 2),
 
@@ -148,7 +156,7 @@ public enum JexlOperator {
      * Less-than-or-equal operator.
      * <br><strong>Syntax:</strong> <code>x &lt;= y</code>
      * <br><strong>Method:</strong> <code>boolean lessThanOrEqual(L x, R 
y);</code>.
-     * @see JexlArithmetic#lessThanOrEqual
+     * @see JexlArithmetic#lessThanOrEqual(Object, Object)
      */
     LTE("<=", "lessThanOrEqual", 2),
 
@@ -156,7 +164,7 @@ public enum JexlOperator {
      * Greater-than operator.
      * <br><strong>Syntax:</strong> <code>x &gt; y</code>
      * <br><strong>Method:</strong> <code>boolean greaterThan(L x, R 
y);</code>.
-     * @see JexlArithmetic#greaterThan
+     * @see JexlArithmetic#greaterThan(Object, Object)
      */
     GT(">", "greaterThan", 2),
 
@@ -164,7 +172,7 @@ public enum JexlOperator {
      * Greater-than-or-equal operator.
      * <br><strong>Syntax:</strong> <code>x &gt;= y</code>
      * <br><strong>Method:</strong> <code>boolean greaterThanOrEqual(L x, R 
y);</code>.
-     * @see JexlArithmetic#greaterThanOrEqual
+     * @see JexlArithmetic#greaterThanOrEqual(Object, Object)
      */
     GTE(">=", "greaterThanOrEqual", 2),
 
@@ -172,7 +180,7 @@ public enum JexlOperator {
      * Contains operator.
      * <br><strong>Syntax:</strong> <code>x =~ y</code>
      * <br><strong>Method:</strong> <code>boolean contains(L x, R y);</code>.
-     * @see JexlArithmetic#contains
+     * @see JexlArithmetic#contains(Object, Object)
      */
     CONTAINS("=~", "contains", 2),
 
@@ -180,7 +188,7 @@ public enum JexlOperator {
      * Starts-with operator.
      * <br><strong>Syntax:</strong> <code>x =^ y</code>
      * <br><strong>Method:</strong> <code>boolean startsWith(L x, R y);</code>.
-     * @see JexlArithmetic#startsWith
+     * @see JexlArithmetic#startsWith(Object, Object)
      */
     STARTSWITH("=^", "startsWith", 2),
 
@@ -188,7 +196,7 @@ public enum JexlOperator {
      * Ends-with operator.
      * <br><strong>Syntax:</strong> <code>x =$ y</code>
      * <br><strong>Method:</strong> <code>boolean endsWith(L x, R y);</code>.
-     * @see JexlArithmetic#endsWith
+     * @see JexlArithmetic#endsWith(Object, Object)
      */
     ENDSWITH("=$", "endsWith", 2),
 
@@ -196,7 +204,7 @@ public enum JexlOperator {
      * Not operator.
      * <br><strong>Syntax:</strong> <code>!x</code>
      * <br><strong>Method:</strong> <code>T not(L x);</code>.
-     * @see JexlArithmetic#not
+     * @see JexlArithmetic#not(Object)
      */
     NOT("!", "not", 1),
 
@@ -204,7 +212,7 @@ public enum JexlOperator {
      * Complement operator.
      * <br><strong>Syntax:</strong> <code>~x</code>
      * <br><strong>Method:</strong> <code>T complement(L x);</code>.
-     * @see JexlArithmetic#complement
+     * @see JexlArithmetic#complement(Object)
      */
     COMPLEMENT("~", "complement", 1),
 
@@ -212,7 +220,7 @@ public enum JexlOperator {
      * Negate operator.
      * <br><strong>Syntax:</strong> <code>-x</code>
      * <br><strong>Method:</strong> <code>T negate(L x);</code>.
-     * @see JexlArithmetic#negate
+     * @see JexlArithmetic#negate(Object)
      */
     NEGATE("-", "negate", 1),
 
@@ -220,7 +228,7 @@ public enum JexlOperator {
      * Positivize operator.
      * <br><strong>Syntax:</strong> <code>+x</code>
      * <br><strong>Method:</strong> <code>T positivize(L x);</code>.
-     * @see JexlArithmetic#positivize
+     * @see JexlArithmetic#positivize(Object)
      */
     POSITIVIZE("+", "positivize", 1),
 
@@ -228,7 +236,7 @@ public enum JexlOperator {
      * Empty operator.
      * <br><strong>Syntax:</strong> <code>empty x</code> or 
<code>empty(x)</code>
      * <br><strong>Method:</strong> <code>boolean empty(L x);</code>.
-     * @see JexlArithmetic#empty
+     * @see JexlArithmetic#empty(Object)
      */
     EMPTY("empty", "empty", 1),
 
@@ -236,7 +244,7 @@ public enum JexlOperator {
      * Size operator.
      * <br><strong>Syntax:</strong> <code>size x</code> or <code>size(x)</code>
      * <br><strong>Method:</strong> <code>int size(L x);</code>.
-     * @see JexlArithmetic#size
+     * @see JexlArithmetic#size(Object)
      */
     SIZE("size", "size", 1),
 
@@ -319,15 +327,15 @@ public enum JexlOperator {
 
     /**
      * Increment pseudo-operator.
-     * <br>No syntax, used as helper for <code>++</code>.
-     * @see JexlArithmetic#increment
+     * <br>No syntax, used as helper for the prefix and postfix versions of 
<code>++</code>.
+     * @see JexlArithmetic#increment(Object)
      */
     INCREMENT("+1", "increment", 1),
 
     /**
      * Decrement pseudo-operator.
-     * <br>No syntax, used as helper for <code>--</code>.
-     * @see JexlArithmetic#decrement
+     * <br>No syntax, used as helper for the prefix and postfix versions of 
<code>--</code>.
+     * @see JexlArithmetic#decrement(Object)
      */
     DECREMENT("-1", "decrement", 1),
 
@@ -336,28 +344,28 @@ public enum JexlOperator {
      * <br><strong>Syntax:</strong> <code>++x</code>
      * <br><strong>Method:</strong> <code>T incrementAndGet(L x);</code>.
      */
-    INCREMENT_AND_GET("++.", "incrementAndGet", INCREMENT),
+    INCREMENT_AND_GET("++.", "incrementAndGet", INCREMENT, 1),
 
     /**
      * Postfix ++, increments and returns the value before incrementing.
      * <br><strong>Syntax:</strong> <code>x++</code>
      * <br><strong>Method:</strong> <code>T getAndIncrement(L x);</code>.
      */
-    GET_AND_INCREMENT(".++", "getAndIncrement", INCREMENT),
+    GET_AND_INCREMENT(".++", "getAndIncrement", INCREMENT, 1),
 
     /**
      * Prefix --, decrements and returns the value after decrementing.
      * <br><strong>Syntax:</strong> <code>--x</code>
      * <br><strong>Method:</strong> <code>T decrementAndGet(L x);</code>.
      */
-    DECREMENT_AND_GET("--.", "decrementAndGet", DECREMENT),
+    DECREMENT_AND_GET("--.", "decrementAndGet", DECREMENT, 1),
 
     /**
      * Postfix --, decrements and returns the value before decrementing.
      * <br><strong>Syntax:</strong> <code>x--</code>
      * <br><strong>Method:</strong> <code>T getAndDecrement(L x);</code>.
      */
-    GET_AND_DECREMENT(".--", "getAndDecrement", DECREMENT),
+    GET_AND_DECREMENT(".--", "getAndDecrement", DECREMENT, 1),
 
     /**
      * Marker for side effect.
@@ -431,23 +439,32 @@ public enum JexlOperator {
      * @param argc the number of parameters for the method
      */
     JexlOperator(final String o, final String m, final int argc) {
-        this.operator = o;
-        this.methodName = m;
-        this.arity = argc;
-        this.base = null;
+        this(o, m, null, argc);
     }
 
     /**
-     * Creates a side-effect operator.
+     * Creates a side effect operator with arity == 2.
      *
      * @param o the operator name
      * @param m the method name associated to this operator in a JexlArithmetic
      * @param b the base operator, ie + for +=
      */
     JexlOperator(final String o, final String m, final JexlOperator b) {
+        this(o, m, b, 2);
+    }
+
+    /**
+     * Creates a side effect operator.
+     *
+     * @param o the operator name
+     * @param m the method name associated to this operator in a JexlArithmetic
+     * @param b the base operator, ie + for +=
+     * @param a the operator arity
+     */
+    JexlOperator(final String o, final String m, final JexlOperator b, final 
int a) {
         this.operator = o;
         this.methodName = m;
-        this.arity = 2;
+        this.arity = a;
         this.base = b;
     }
 
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 e24bf8f1..196c0fcf 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -50,6 +50,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
     protected int depth = Integer.MAX_VALUE;
     /** Arrow symbol. */
     protected String arrow = "->";
+    /** EOL. */
+    protected String lf = "\n";
 
     /**
      * Creates a Debugger.
@@ -234,6 +236,16 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return this;
     }
 
+    /**
+     * Sets this debugger line-feed string.
+     * @param lf the string used to delineate lines (usually "\" or "")
+     * @return this debugger instance
+     */
+    public Debugger lineFeed(final String lf) {
+        this.lf = lf;
+        return this;
+    }
+
     /**
      * Checks if a child node is the cause to debug &amp; adds its 
representation to the rebuilt expression.
      * @param node the child node
@@ -264,7 +276,6 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     private static boolean isStatement(JexlNode child) {
         return child instanceof ASTJexlScript
-                || child instanceof ASTJexlLambda
                 || child instanceof ASTBlock
                 || child instanceof ASTIfStatement
                 || child instanceof ASTForeachStatement
@@ -313,7 +324,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         if (!isStatement(child) && !semicolTerminated(builder)) {
             builder.append(';');
             if (indent > 0) {
-                builder.append('\n');
+                builder.append(lf);
             } else {
                 builder.append(' ');
             }
@@ -537,7 +548,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         builder.append('{');
         if (indent > 0) {
             indentLevel += 1;
-            builder.append('\n');
+            builder.append(lf);
         } else {
             builder.append(' ');
         }
@@ -554,6 +565,9 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 }
             }
         }
+        if (!Character.isSpaceChar(builder.charAt(builder.length() - 1))) {
+            builder.append(' ');
+        }
         builder.append('}');
         return data;
     }
@@ -615,15 +629,45 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return check(node, "break", data);
     }
 
+
     @Override
     protected Object visit(final ASTForeachStatement node, final Object data) {
+        final int form = node.getLoopForm();
         builder.append("for(");
-        accept(node.jjtGetChild(0), data);
-        builder.append(" : ");
-        accept(node.jjtGetChild(1), data);
-        builder.append(") ");
-        if (node.jjtGetNumChildren() > 2) {
-            acceptStatement(node.jjtGetChild(2), data);
+        final JexlNode body;
+        if (form == 0) {
+            // for( .. : ...)
+            accept(node.jjtGetChild(0), data);
+            builder.append(" : ");
+            accept(node.jjtGetChild(1), data);
+            builder.append(") ");
+            body = node.jjtGetNumChildren() > 2? node.jjtGetChild(2) : null;
+        } else {
+            // for( .. ; ... ; ..)
+            int nc = 0;
+            // first child is var declaration(s)
+            final JexlNode vars = (form & 1) != 0 ? node.jjtGetChild(nc++) : 
null;
+            final JexlNode predicate = (form & 2) != 0 ? 
node.jjtGetChild(nc++) : null;
+            // the loop step
+            final JexlNode step = (form & 4) != 0 ? node.jjtGetChild(nc++) : 
null;
+            // last child is body
+            body = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
+            if (vars != null) {
+                accept(vars, data);
+            }
+            builder.append("; ");
+            if (predicate != null) {
+                accept(predicate, data);
+            }
+            builder.append("; ");
+            if (step != null) {
+                accept(step, data);
+            }
+            builder.append(") ");
+        }
+        // the body
+        if (body != null) {
+            accept(body, data);
         } else {
             builder.append(';');
         }
@@ -1047,6 +1091,36 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return data;
     }
 
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        final int num = node.jjtGetNumChildren();
+        if (num > 0) {
+            // var, let, const
+            accept(node.jjtGetChild(0), data);
+            for (int i = 1; i < num; ++i) {
+                builder.append(", ");
+                JexlNode child = node.jjtGetChild(i);
+                if (child instanceof ASTAssignment) {
+                    ASTAssignment assign = (ASTAssignment) child;
+                    int nc = assign.jjtGetNumChildren();
+                    ASTVar var = (ASTVar) assign.jjtGetChild(0);
+                    builder.append(var.getName());
+                    if (nc > 1) {
+                        builder.append(" = ");
+                        accept(assign.jjtGetChild(1), data);
+                    }
+                } else if (child instanceof ASTVar) {
+                    ASTVar var = (ASTVar) child;
+                    builder.append(var.getName());
+                } else {
+                    // that's odd
+                    accept(child, data);
+                }
+            }
+        }
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTWhileStatement node, final Object data) {
         builder.append("while (");
@@ -1132,7 +1206,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
 
     @Override
     protected Object visit(final ASTGetDecrementNode node, final Object data) {
-        return postfixChild(node, "++", data);
+        return postfixChild(node, "--", data);
     }
 
     @Override
@@ -1147,7 +1221,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
 
     @Override
     protected Object visit(final ASTIncrementGetNode node, final Object data) {
-        return prefixChild(node, "--", data);
+        return prefixChild(node, "++", data);
     }
 
     @Override
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java 
b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index c701b6fd..cf3c8d49 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -74,7 +74,7 @@ public class Engine extends JexlEngine {
      */
     private static final class UberspectHolder {
         /** The default uberspector that handles all introspection patterns. */
-        private static final Uberspect UBERSPECT =
+        static final Uberspect UBERSPECT =
                 new Uberspect(LogFactory.getLog(JexlEngine.class),
                         JexlUberspect.JEXL_STRATEGY,
                         JexlPermissions.parse());
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 7b9861e9..2ce7923f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -19,6 +19,7 @@ package org.apache.commons.jexl3.internal;
 
 import java.util.Iterator;
 import java.util.concurrent.Callable;
+import java.util.function.Consumer;
 
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlContext;
@@ -173,6 +174,7 @@ public class Interpreter extends InterpreterBase {
     public Object getAttribute(final Object object, final Object attribute) {
         return getAttribute(object, attribute, null);
     }
+
     /**
      * Sets an attribute of an object.
      *
@@ -627,7 +629,7 @@ public class Interpreter extends InterpreterBase {
         return node.getLoopForm() == 0 ? forIterator(node, data) : 
forLoop(node, data);
     }
 
-    protected Object forIterator(final ASTForeachStatement node, final Object 
data) {
+    private Object forIterator(final ASTForeachStatement node, final Object 
data) {
         Object result = null;
         /* first objectNode is the loop variable */
         final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
@@ -704,7 +706,7 @@ public class Interpreter extends InterpreterBase {
         return result;
     }
 
-    protected Object forLoop(final ASTForeachStatement node, final Object 
data) {
+    private Object forLoop(final ASTForeachStatement node, final Object data) {
         Object result = null;
         int nc;
         int form = node.getLoopForm();
@@ -733,22 +735,24 @@ public class Interpreter extends InterpreterBase {
             }
             // initialize after eventual creation of local lexical frame
             init.jjtAccept(this, data);
-            nc = 1;
+            // other inits
+            for (JexlNode moreAssignment = node.jjtGetChild(nc);
+                 moreAssignment instanceof ASTAssignment;
+                 moreAssignment = node.jjtGetChild(++nc)) {
+                moreAssignment.jjtAccept(this, data);
+            }
         } else {
             locals = null;
             nc = 0;
         }
-        Object forEach = null;
         try {
             // the loop condition
             final JexlNode predicate = (form & 2) != 0? node.jjtGetChild(nc++) 
: null;
             // the loop step
             final JexlNode step = (form & 4) != 0? node.jjtGetChild(nc++) : 
null;
             // last child is body
-            final JexlNode statement = (form & 8) != 0 ? 
node.jjtGetChild(nc++) : null;
-            // get an iterator for the collection/array etc via the 
introspector.
-            final Iterator<?> itemsIterator = null;
-            int cnt = 0;
+            final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) 
: null;
+            // while(predicate())...
             while (predicate == null || 
arithmetic.toBoolean(predicate.jjtAccept(this, data))) {
                 cancelCheck(node);
                 // the body
@@ -768,8 +772,6 @@ public class Interpreter extends InterpreterBase {
                 }
             }
         } finally {
-            //  closeable iterator handling
-            closeIfSupported(forEach);
             // restore lexical frame
             if (locals != null) {
                 block = block.pop();
@@ -1368,15 +1370,6 @@ public class Interpreter extends InterpreterBase {
         return executeAssign(node, JexlOperator.INCREMENT_AND_GET, data);
     }
 
-    /**
-     * Helper for postfix assignment operators.
-     * @param operator the operator
-     * @return true if operator is a postfix operator (x++, y--)
-     */
-    private static boolean isPostfix(JexlOperator operator) {
-        return operator == JexlOperator.GET_AND_INCREMENT || operator == 
JexlOperator.GET_AND_DECREMENT;
-    }
-
     /**
      * Executes an assignment with an optional side-effect operator.
      * @param node     the node
@@ -1388,9 +1381,9 @@ public class Interpreter extends InterpreterBase {
         cancelCheck(node);
         // left contains the reference to assign to
         final JexlNode left = node.jjtGetChild(0);
-        ASTIdentifier var = null;
+        final ASTIdentifier var;
         Object object = null;
-        int symbol = -1;
+        final int symbol;
         // check var decl with assign is ok
         if (left instanceof ASTIdentifier) {
             var = (ASTIdentifier) left;
@@ -1404,50 +1397,54 @@ public class Interpreter extends InterpreterBase {
                     return undefinedVariable(var, var.getName());
                 }
             }
+        } else {
+            var = null;
+            symbol = -1;
         }
         boolean antish = options.isAntish();
         // 0: determine initial object & property:
         final int last = left.jjtGetNumChildren() - 1;
         // right is the value expression to assign
-        Object right = node.jjtGetNumChildren() < 2? null: 
node.jjtGetChild(1).jjtAccept(this, data);
-        // previous value for postfix assignment operators (x++, x--)
-        Object actual = null;
+       final  Object right = node.jjtGetNumChildren() < 2? null: 
node.jjtGetChild(1).jjtAccept(this, data);
+        // actual value to return, right in most cases
+        Object actual = right;
         // a (var?) v = ... expression
         if (var != null) {
             if (symbol >= 0) {
                 // check we are not assigning a symbol itself
                 if (last < 0) {
-                    if (assignop != null) {
-                        final Object self = actual = getVariable(frame, block, 
var);
-                        right = operators.tryAssignOverload(node, assignop, 
self, right);
-                        if (right == JexlOperator.ASSIGN) {
-                            return self;
+                    if (assignop == null) {
+                        // make the closure accessible to itself, ie capture 
the currently set variable after frame creation
+                        if (right instanceof Closure) {
+                            ((Closure) right).setCaptured(symbol, right);
                         }
+                        frame.set(symbol, right);
+                    } else {
+                        // go through potential overload
+                        final Object self = getVariable(frame, block, var);
+                        final Consumer<Object> f = r -> frame.set(symbol, r);
+                        actual = operators.tryAssignOverload(node, assignop, 
f, self, right);
                     }
-                    frame.set(symbol, right);
-                    // make the closure accessible to itself, ie capture the 
currently set variable after frame creation
-                    if (right instanceof Closure) {
-                        ((Closure) right).setCaptured(symbol, right);
-                    }
-                    return isPostfix(assignop)? actual : right; // 1
+                    return actual; // 1
                 }
                 object = getVariable(frame, block, var);
                 // top level is a symbol, can not be an antish var
                 antish = false;
             } else {
                 // check we are not assigning direct global
+                final String name = var.getName();
                 if (last < 0) {
-                    if (assignop != null) {
-                        final Object self = actual = 
context.get(var.getName());
-                        right = operators.tryAssignOverload(node, assignop, 
self, right);
-                        if (right == JexlOperator.ASSIGN) {
-                            return self;
-                        }
+                    if (assignop == null) {
+                        setContextVariable(node, name, right);
+                    } else {
+                        // go through potential overload
+                        final Object self = context.get(name);
+                        final Consumer<Object> f = r ->  
setContextVariable(node, name, r);
+                        actual = operators.tryAssignOverload(node, assignop, 
f, self, right);
                     }
-                    setContextVariable(node, var.getName(), right);
-                    return isPostfix(assignop)? actual : right; // 2
+                    return actual; // 2
                 }
-                object = context.get(var.getName());
+                object = context.get(name);
                 // top level accesses object, can not be an antish var
                 if (object != null) {
                     antish = false;
@@ -1510,19 +1507,18 @@ public class Interpreter extends InterpreterBase {
         if (propertyId != null) {
             // deal with creating/assignining antish variable
             if (antish && ant != null && object == null && 
!propertyId.isSafe() && !propertyId.isExpression()) {
-                if (last > 0) {
-                    ant.append('.');
-                }
+                ant.append('.');
                 ant.append(propertyId.getName());
-                if (assignop != null) {
+                final String name = ant.toString();
+                if (assignop == null) {
+                    setContextVariable(propertyNode, name, right);
+                } else {
                     final Object self = actual = context.get(ant.toString());
-                    right = operators.tryAssignOverload(node, assignop, self, 
right);
-                    if (right == JexlOperator.ASSIGN) {
-                        return self;
-                    }
+                    final JexlNode pnode = propertyNode;
+                    final Consumer<Object> assign = r -> 
setContextVariable(pnode, name, r);
+                    actual = operators.tryAssignOverload(node, assignop, 
assign, self, right);
                 }
-                setContextVariable(propertyNode, ant.toString(), right);
-                return isPostfix(assignop)? actual : right; // 3
+                return actual; // 3
             }
             // property of an object ?
             property = evalIdentifier(propertyId);
@@ -1546,15 +1542,26 @@ public class Interpreter extends InterpreterBase {
             return unsolvableProperty(objectNode, "<null>.<?>", true, null);
         }
         // 3: one before last, assign
-        if (assignop != null) {
-            final Object self = actual = getAttribute(object, property, 
propertyNode);
-            right = operators.tryAssignOverload(node, assignop, self, right);
-            if (right == JexlOperator.ASSIGN) {
-                return self;
-            }
+        if (assignop == null) {
+            setAttribute(object, property, right, propertyNode);
+        } else {
+            final Object self = getAttribute(object, property, propertyNode);
+            final Object o = object;
+            final JexlNode n = propertyNode;
+            final Consumer<Object> assign = r ->  setAttribute(o, property, r, 
n);
+            actual = operators.tryAssignOverload(node, assignop, assign, self, 
right);
+        }
+        return actual;
+    }
+
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        final int argc = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < argc; i++) {
+            result = node.jjtGetChild(i).jjtAccept(this, data);
         }
-        setAttribute(object, property, right, propertyNode);
-        return isPostfix(assignop)? actual : right; // 4
+        return result;
     }
 
     @Override
@@ -1756,7 +1763,7 @@ public class Interpreter extends InterpreterBase {
                         return call.eval(mCALL);
                     }
                     // functor is a var, may be method is a global one ?
-                    if (isavar && target == context) {
+                    if (isavar) {
                         if (call.isContextMethod(methodName, argv)) {
                             return call.eval(methodName);
                         }
@@ -1824,8 +1831,8 @@ public class Interpreter extends InterpreterBase {
                 }
             }
             boolean narrow = false;
-            JexlMethod ctor = null;
             Funcall funcall = null;
+            JexlMethod ctor;
             while (true) {
                 // try as stated
                 ctor = uberspect.getConstructor(target, argv);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java 
b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index aa548e0e..f0da705e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3.internal;
 
-import java.lang.reflect.Method;
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
@@ -26,6 +25,9 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.parser.JexlNode;
 
+import java.lang.reflect.Method;
+import java.util.function.Consumer;
+
 /**
  * Helper class to deal with operator overloading and specifics.
  * @since 3.0
@@ -95,7 +97,7 @@ public class Operators {
      * @param args     the arguments
      * @return the result of the operator evaluation or TRY_FAILED
      */
-    protected Object tryOverload(final JexlNode node, final JexlOperator 
operator, final Object... args) {
+    protected Object tryOverload(final JexlNode node, final JexlOperator 
operator, Object... args) {
         if (operators != null && operators.overloads(operator)) {
             final JexlArithmetic arithmetic = interpreter.arithmetic;
             final boolean cache = interpreter.cache;
@@ -113,18 +115,39 @@ public class Operators {
                 final JexlMethod vm = operators.getOperator(operator, args);
                 if (vm != null && !isArithmetic(vm)) {
                     final Object result = vm.invoke(arithmetic, args);
-                    if (cache) {
+                    if (cache && !vm.tryFailed(result)) {
                         node.jjtSetValue(vm);
                     }
                     return result;
                 }
             } catch (final Exception xany) {
-                return interpreter.operatorError(node, operator, xany);
+                // ignore return if lenient, will return try_failed
+                interpreter.operatorError(node, operator, xany);
             }
         }
         return JexlEngine.TRY_FAILED;
     }
 
+    /**
+     * Helper for postfix assignment operators.
+     * @param operator the operator
+     * @return true if operator is a postfix operator (x++, y--)
+     */
+    private static boolean isPostfix(JexlOperator operator) {
+        return operator == JexlOperator.GET_AND_INCREMENT || operator == 
JexlOperator.GET_AND_DECREMENT;
+    }
+
+    /**
+     * Tidy arguments based on operator arity.
+     * <p>The interpreter may add a null to the arguments of operator 
expecting only one parameter.</p>
+     * @param operator the operator
+     * @param args the arguements (as seen by the interpreter)
+     * @return the tidied arguments
+     */
+    private Object[] arguments(JexlOperator operator, Object...args) {
+        return operator.getArity() == 1 && args.length > 1 ? new 
Object[]{args[0]} : args;
+    }
+
     /**
      * Evaluates an assign operator.
      * <p>
@@ -139,72 +162,91 @@ public class Operators {
      *         JexlEngine.TRY_FAILED if no operation was performed,
      *         the value to use as the side effect argument otherwise
      */
-    protected Object tryAssignOverload(final JexlNode node, final JexlOperator 
operator, final Object...args) {
+    protected Object tryAssignOverload(final JexlNode node,
+                                       final JexlOperator operator,
+                                       final Consumer<Object> assignFun,
+                                       final Object...args) {
         final JexlArithmetic arithmetic = interpreter.arithmetic;
         if (args.length < operator.getArity()) {
             return JexlEngine.TRY_FAILED;
         }
-        // try to call overload with side effect
-        Object result = tryOverload(node, operator, args);
-        if (result != JexlEngine.TRY_FAILED) {
-            return result;
-        }
-        // call base operator
-        final JexlOperator base = operator.getBaseOperator();
-        if (base == null) {
-            throw new IllegalArgumentException("must be called with a 
side-effect operator");
-        }
-        if (operators != null && operators.overloads(base)) {
-            // in case there is an overload on the base operator
-            try {
-                final JexlMethod vm = operators.getOperator(base, args);
-                if (vm != null) {
-                    result = vm.invoke(arithmetic, args);
-                    if (result != JexlEngine.TRY_FAILED) {
-                        return result;
-                    }
+        Object result;
+        try {
+        // if some overloads exist...
+        if (operators != null) {
+            // try to call overload with side effect; the object is modified
+            result = tryOverload(node, operator, arguments(operator, args));
+            if (result != JexlEngine.TRY_FAILED) {
+                return result; // 1
+            }
+            // try to call base overload (ie + for +=)
+            final JexlOperator base = operator.getBaseOperator();
+            if (base != null && operators.overloads(base)) {
+                result = tryOverload(node, base, arguments(base, args));
+                if (result != JexlEngine.TRY_FAILED) {
+                    assignFun.accept(result);
+                    return isPostfix(operator) ? args[0] : result; // 2
                 }
-            } catch (final Exception xany) {
-                interpreter.operatorError(node, base, xany);
             }
         }
         // base eval
-        try {
-            switch (operator) {
-                case SELF_ADD:
-                    return arithmetic.add(args[0], args[1]);
-                case SELF_SUBTRACT:
-                    return arithmetic.subtract(args[0], args[1]);
-                case SELF_MULTIPLY:
-                    return arithmetic.multiply(args[0], args[1]);
-                case SELF_DIVIDE:
-                    return arithmetic.divide(args[0], args[1]);
-                case SELF_MOD:
-                    return arithmetic.mod(args[0], args[1]);
-                case SELF_AND:
-                    return arithmetic.and(args[0], args[1]);
-                case SELF_OR:
-                    return arithmetic.or(args[0], args[1]);
-                case SELF_XOR:
-                    return arithmetic.xor(args[0], args[1]);
-                case SELF_SHIFTLEFT:
-                    return arithmetic.shiftLeft(args[0], args[1]);
-                case SELF_SHIFTRIGHT:
-                    return arithmetic.shiftRight(args[0], args[1]);
-                case SELF_SHIFTRIGHTU:
-                    return arithmetic.shiftRightUnsigned(args[0], args[1]);
-                case INCREMENT_AND_GET:
-                case GET_AND_INCREMENT:
-                    return arithmetic.increment(args[0]);
-                case DECREMENT_AND_GET:
-                case GET_AND_DECREMENT:
-                    return arithmetic.decrement(args[0]);
-                default:
-                    // unexpected, new operator added?
-                    throw new 
UnsupportedOperationException(operator.getOperatorSymbol());
+        switch (operator) {
+            case SELF_ADD:
+                result = arithmetic.add(args[0], args[1]);
+                break;
+            case SELF_SUBTRACT:
+                result = arithmetic.subtract(args[0], args[1]);
+                break;
+            case SELF_MULTIPLY:
+                result = arithmetic.multiply(args[0], args[1]);
+                break;
+            case SELF_DIVIDE:
+                result = arithmetic.divide(args[0], args[1]);
+                break;
+            case SELF_MOD:
+                result = arithmetic.mod(args[0], args[1]);
+                break;
+            case SELF_AND:
+                result = arithmetic.and(args[0], args[1]);
+                break;
+            case SELF_OR:
+                result = arithmetic.or(args[0], args[1]);
+                break;
+            case SELF_XOR:
+                result = arithmetic.xor(args[0], args[1]);
+                break;
+            case SELF_SHIFTLEFT:
+                result = arithmetic.shiftLeft(args[0], args[1]);
+                break;
+            case SELF_SHIFTRIGHT:
+                result = arithmetic.shiftRight(args[0], args[1]);
+                break;
+            case SELF_SHIFTRIGHTU:
+                result = arithmetic.shiftRightUnsigned(args[0], args[1]);
+                break;
+            case INCREMENT_AND_GET:
+                result = arithmetic.increment(args[0]);
+                break;
+            case DECREMENT_AND_GET:
+                result = arithmetic.decrement(args[0]);
+                break;
+            case GET_AND_INCREMENT:
+                result = args[0];
+                assignFun.accept(arithmetic.increment(result));
+                return result; // 3
+            case GET_AND_DECREMENT: {
+                result = args[0];
+                assignFun.accept(arithmetic.decrement(result));
+                return result; // 4
+            }
+            default:
+                // unexpected, new operator added?
+                throw new 
UnsupportedOperationException(operator.getOperatorSymbol());
             }
+            assignFun.accept(result);
+            return result; // 5
         } catch (final Exception xany) {
-            interpreter.operatorError(node, base, xany);
+            interpreter.operatorError(node, operator, xany);
         }
         return JexlEngine.TRY_FAILED;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java 
b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
index ee14bbd8..2951ff60 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -84,7 +84,7 @@ public final class Scope {
     public Scope(final Scope scope, final String... parameters) {
         if (parameters != null) {
             parms = parameters.length;
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
             for (int p = 0; p < parms; ++p) {
                 namedVariables.put(parameters[p], p);
             }
@@ -140,10 +140,10 @@ public final class Scope {
             final Integer pr = parent.getSymbol(name, true);
             if (pr != null) {
                 if (capturedVariables == null) {
-                    capturedVariables = new LinkedHashMap<Integer, Integer>();
+                    capturedVariables = new LinkedHashMap<>();
                 }
                 if (namedVariables == null) {
-                    namedVariables = new LinkedHashMap<String, Integer>();
+                    namedVariables = new LinkedHashMap<>();
                 }
                 register = namedVariables.size();
                 namedVariables.put(name, register);
@@ -193,7 +193,7 @@ public final class Scope {
      */
     public int declareParameter(final String name) {
         if (namedVariables == null) {
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
         } else if (vars > 0) {
             throw new IllegalStateException("cant declare parameters after 
variables");
         }
@@ -214,9 +214,9 @@ public final class Scope {
      * @param name the variable name
      * @return the register index storing this variable
      */
-    public int declareVariable(final String name, boolean lexical, boolean 
constant) {
+    public int declareVariable(final String name) {
         if (namedVariables == null) {
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
         }
         Integer register = namedVariables.get(name);
         if (register == null) {
@@ -228,7 +228,7 @@ public final class Scope {
                 final Integer pr = parent.getSymbol(name, true);
                 if (pr != null) {
                     if (capturedVariables == null) {
-                        capturedVariables = new LinkedHashMap<Integer, 
Integer>();
+                        capturedVariables = new LinkedHashMap<>();
                     }
                     capturedVariables.put(register, pr);
                 }
@@ -285,7 +285,7 @@ public final class Scope {
      */
     public String[] getCapturedVariables() {
         if (capturedVariables != null) {
-            final List<String> captured = new ArrayList<String>(vars);
+            final List<String> captured = new ArrayList<>(vars);
             for (final Map.Entry<String, Integer> entry : 
namedVariables.entrySet()) {
                 final int symnum = entry.getValue();
                 if (symnum >= parms && capturedVariables.containsKey(symnum)) {
@@ -293,7 +293,7 @@ public final class Scope {
                 }
             }
             if (!captured.isEmpty()) {
-                return captured.toArray(new String[captured.size()]);
+                return captured.toArray(new String[0]);
             }
         }
         return EMPTY_STRS;
@@ -328,7 +328,7 @@ public final class Scope {
      * @param bound number of known bound parameters (curry)
      * @return the parameter names
      */
-    protected String[] getParameters(final int bound) {
+     String[] getParameters(final int bound) {
         final int unbound = parms - bound;
         if ((namedVariables == null) || (unbound <= 0)) {
             return EMPTY_STRS;
@@ -352,14 +352,14 @@ public final class Scope {
         if ((namedVariables == null) || (vars <= 0)) {
             return EMPTY_STRS;
         }
-        final List<String> locals = new ArrayList<String>(vars);
+        final List<String> locals = new ArrayList<>(vars);
         for (final Map.Entry<String, Integer> entry : 
namedVariables.entrySet()) {
             final int symnum = entry.getValue();
             if (symnum >= parms && (capturedVariables == null || 
!capturedVariables.containsKey(symnum))) {
                 locals.add(entry.getKey());
             }
         }
-        return locals.toArray(new String[locals.size()]);
+        return locals.toArray(new String[0]);
     }
 
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java 
b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
index 5964d348..9da2182d 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -21,7 +21,7 @@ import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.parser.*;
 
 /**
- * Fully abstract to avoid public interface exposition.
+ * Concrete visitor base, used for feature and operator controllers.
  */
 public class ScriptVisitor extends ParserVisitor {
     /**
@@ -116,6 +116,11 @@ public class ScriptVisitor extends ParserVisitor {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        return visitNode(node, data);
+    }
+
     @Override
     protected Object visit(final ASTReference node, final Object data) {
         return visitNode(node, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index b5db9195..2edef122 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -237,7 +237,7 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Gets the lexical unit currently used by this parser.
+     * Gets the lexical unit used by this parser.
      * @return the named register map
      */
     protected LexicalUnit getUnit() {
@@ -399,7 +399,7 @@ public abstract class JexlParser extends StringParser {
         if (scope == null) {
             scope = new Scope(null);
         }
-        final int symbol = scope.declareVariable(name, true, true);
+        final int symbol = scope.declareVariable(name);
         variable.setSymbol(symbol, name);
         variable.setLexical(true);
         if (scope.isCapturedSymbol(symbol)) {
@@ -433,7 +433,7 @@ public abstract class JexlParser extends StringParser {
         if (scope == null) {
             scope = new Scope(null);
         }
-        final int symbol = scope.declareVariable(name, lexical, constant);
+        final int symbol = scope.declareVariable(name);
         variable.setSymbol(symbol, name);
         variable.setLexical(lexical);
         variable.setConstant(constant);
@@ -701,7 +701,6 @@ public abstract class JexlParser extends StringParser {
     protected void throwParsingException(final Token parsed) {
         JexlInfo xinfo  = null;
         String msg = "unrecoverable state";
-        JexlException.Parsing xparse = null;
         Token token = parsed;
         if (token == null) {
             token = this.getToken(0);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt 
b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index 18ad3509..074c8471 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -285,9 +285,11 @@ TOKEN_MGR_DECLS : {
 <*> TOKEN :
 {
   < STRING_LITERAL:
-    "\"" (~["\"","\\","\n","\r","\u2028","\u2029"] | "\\" 
~["\n","\r","\u2028","\u2029"])* "\""
+    "\"" (~["\"","\\","\n","\r","\t","\f","\b","\u2028","\u2029"]
+          | "\\" ~["\n","\r","\t","\f","\b","\u2028","\u2029"])* "\""
   |
-    "'" (~["'","\\","\n","\r","\u2028","\u2029"] | "\\" 
~["\n","\r","\u2028","\u2029"])* "'"
+    "'" (~["'","\\","\n","\r","\t","\f","\b","\u2028","\u2029"]
+         | "\\" ~["\n","\r","\t","\f","\b","\u2028","\u2029"])* "'"
   > { popDot(); } /* Revert state to default if was DOT_ID. */
 }
 
@@ -301,7 +303,7 @@ TOKEN_MGR_DECLS : {
 <*> TOKEN :
 {
   < REGEX_LITERAL:
-    "~" "/" (~["/","\n","\r","\u2028","\u2029"] | "\\" "/" )* "/"
+    "~" "/" (~["/","\n","\r","\t","\f","\b","\u2028","\u2029"] | "\\" "/" )* 
"/"
   > { popDot(); } /* Revert state to default if was DOT_ID. */
 }
 
@@ -466,11 +468,11 @@ void ForEachVar() #Reference : {}
 
 void Var() #void : {}
 {
-    <VAR> DefineVar() (<COMMA> DefineVar())*
+    (<VAR> DefineVar() (<COMMA> DefineVar())*) #DefineVars(>1)
     |
-    <LET> DefineLet() (<COMMA> DefineLet())*
+    (<LET> DefineLet() (<COMMA> DefineLet())*) #DefineVars(>1)
     |
-    <CONST> DefineConst() (<COMMA> DefineConst())*
+    (<CONST> DefineConst() (<COMMA> DefineConst())*) #DefineVars(>1)
 }
 
  void DefineVar() #void : {}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java 
b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
index f03d4a99..e457c417 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -16,8 +16,6 @@
  */
 package org.apache.commons.jexl3.parser;
 
-import org.apache.commons.jexl3.JexlOperator;
-
 /**
  * Fully abstract to avoid public interface exposition.
  */
@@ -64,6 +62,8 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTVar node, Object data);
 
+    protected abstract Object visit(ASTDefineVars node, Object data);
+
     protected abstract Object visit(ASTReference node, Object data);
 
     protected abstract Object visit(ASTTernaryNode node, Object data);
@@ -194,13 +194,13 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTSetShiftRightUnsignedNode node, final 
Object data);
 
-    protected abstract Object visit(final ASTGetDecrementNode node, final 
Object data);
+    protected abstract Object visit(ASTGetDecrementNode node, final Object 
data);
 
-    protected abstract Object visit(final ASTGetIncrementNode node, final 
Object data);
+    protected abstract Object visit(ASTGetIncrementNode node, final Object 
data);
 
-    protected abstract Object visit(final ASTDecrementGetNode node, final 
Object data);
+    protected abstract Object visit(ASTDecrementGetNode node, final Object 
data);
 
-    protected abstract Object visit(final ASTIncrementGetNode node, final 
Object data);
+    protected abstract Object visit(ASTIncrementGetNode node, final Object 
data);
 
     protected abstract Object visit(ASTJxltLiteral node, Object data);
 
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 8c1ca580..61778347 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -33,8 +33,10 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -384,6 +386,143 @@ public class ArithmeticTest extends JexlTestCase {
         }
     }
 
+    @Test
+    public void testMinusMinusPrefix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 2));
+        asserter.setVariable("aShort", new Short((short) 3));
+        asserter.setVariable("anInteger", new Integer(4));
+        asserter.setVariable("aLong", new Long(5));
+        asserter.setVariable("aFloat", new Float(6.6));
+        asserter.setVariable("aDouble", new Double(7.7));
+        asserter.setVariable("aBigInteger", new BigInteger("8"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("--aByte", new Byte((byte) 1));
+        asserter.assertExpression("--aShort", new Short((short) 2));
+        asserter.assertExpression("--anInteger", new Integer(3));
+        asserter.assertExpression("--aLong", new Long(4));
+        asserter.assertExpression("--aFloat", new Float(5.6));
+        asserter.assertExpression("--aDouble", new Double(6.7));
+        asserter.assertExpression("--aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("--aBigDecimal", new BigDecimal("8.9"));
+
+        asserter.failExpression("aString--", "--", String::contains);
+    }
+
+    @Test
+    public void testMinusMinusPostfix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 2));
+        asserter.setVariable("aShort", new Short((short) 3));
+        asserter.setVariable("anInteger", new Integer(4));
+        asserter.setVariable("aLong", new Long(5));
+        asserter.setVariable("aFloat", new Float(6.6));
+        asserter.setVariable("aDouble", new Double(7.7));
+        asserter.setVariable("aBigInteger", new BigInteger("8"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("aByte--",new Byte((byte) 2));
+        asserter.assertExpression("aShort--", new Short((short) 3));
+        asserter.assertExpression("anInteger--", new Integer(4));
+        asserter.assertExpression("aLong--", new Long(5));
+        asserter.assertExpression("aFloat--", new Float(6.6));
+        asserter.assertExpression("aDouble--", new Double(7.7));
+        asserter.assertExpression("aBigInteger--", new BigInteger("8"));
+        asserter.assertExpression("aBigDecimal--", new BigDecimal("9.9"));
+
+        asserter.assertExpression("aByte", new Byte((byte) 1));
+        asserter.assertExpression("aShort", new Short((short) 2));
+        asserter.assertExpression("anInteger", new Integer(3));
+        asserter.assertExpression("aLong", new Long(4));
+        asserter.assertExpression("aFloat", new Float(5.6));
+        asserter.assertExpression("aDouble", new Double(6.7));
+        asserter.assertExpression("aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("aBigDecimal", new BigDecimal("8.9"));
+
+        asserter.failExpression("aString--", "--", String::contains);
+    }
+
+    @Test
+    public void testPlusPlusPrefix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 0));
+        asserter.setVariable("aShort", new Short((short) 1));
+        asserter.setVariable("anInteger", new Integer(2));
+        asserter.setVariable("aLong", new Long(3));
+        asserter.setVariable("aFloat", new Float(4.4));
+        asserter.setVariable("aDouble", new Double(5.5));
+        asserter.setVariable("aBigInteger", new BigInteger("6"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("++aByte", new Byte((byte) 1));
+        asserter.assertExpression("++aShort", new Short((short) 2));
+        asserter.assertExpression("++anInteger", new Integer(3));
+        asserter.assertExpression("++aLong", new Long(4));
+        asserter.assertExpression("++aFloat", new Float(5.4));
+        asserter.assertExpression("++aDouble", new Double(6.5));
+        asserter.assertExpression("++aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("++aBigDecimal", new BigDecimal("8.7"));
+
+        asserter.failExpression("++aString", "++", String::contains);
+    }
+
+    @Test
+    public void testPlusPlusPostfix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 0));
+        asserter.setVariable("aShort", new Short((short) 1));
+        asserter.setVariable("anInteger", new Integer(2));
+        asserter.setVariable("aLong", new Long(3));
+        asserter.setVariable("aFloat", new Float(4.4));
+        asserter.setVariable("aDouble", new Double(5.5));
+        asserter.setVariable("aBigInteger", new BigInteger("6"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("aByte++", new Byte((byte) 0));
+        asserter.assertExpression("aShort++", new Short((short) 1));
+        asserter.assertExpression("anInteger++", new Integer(2));
+        asserter.assertExpression("aLong++", new Long(3));
+        asserter.assertExpression("aFloat++", new Float(4.4));
+        asserter.assertExpression("aDouble++", new Double(5.5));
+        asserter.assertExpression("aBigInteger++", new BigInteger("6"));
+        asserter.assertExpression("aBigDecimal++", new BigDecimal("7.7"));
+
+        asserter.assertExpression("aByte", new Byte((byte) 1));
+        asserter.assertExpression("aShort", new Short((short) 2));
+        asserter.assertExpression("anInteger", new Integer(3));
+        asserter.assertExpression("aLong", new Long(4));
+        asserter.assertExpression("aFloat", new Float(5.4));
+        asserter.assertExpression("aDouble", new Double(6.5));
+        asserter.assertExpression("aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("aBigDecimal", new BigDecimal("8.7"));
+
+        asserter.failExpression("aString++", "++", String::contains);
+    }
+
+    @Test
+    public void testNarrowBig() throws Exception {
+        List<String> ls = Arrays.asList("zero", "one", "two");
+        asserter.setVariable("list",ls);
+        asserter.setVariable("aBigDecimal", new BigDecimal("1"));
+        asserter.setVariable("aBigInteger", new BigDecimal("1"));
+        asserter.assertExpression("list.get(aBigDecimal)", "one");
+        asserter.assertExpression("list.get(aBigInteger)", "one");
+    }
+
+    @Test
+    public void testNarrowBigDecimal() throws Exception {
+        asserter.setVariable("bi420", BigInteger.valueOf(420));
+        asserter.setVariable("bi10", BigInteger.valueOf(10));
+        asserter.setVariable("bd420", new BigDecimal("420"));
+        asserter.setVariable("bd10", new BigDecimal("10"));
+        asserter.assertExpression("420 / bi10", 42);
+        asserter.assertExpression("420l / bi10", 42L);
+        asserter.assertExpression("bi420 / 420", 1);
+        asserter.assertExpression("bi420 / 420l", 1L);
+        asserter.assertExpression("bd420 / 10", new BigDecimal("42"));
+    }
+
     /**
      * test some simple mathematical calculations
      */
diff --git a/src/test/java/org/apache/commons/jexl3/ForEachTest.java 
b/src/test/java/org/apache/commons/jexl3/ForEachTest.java
index 48308f0a..0207cfbc 100644
--- a/src/test/java/org/apache/commons/jexl3/ForEachTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ForEachTest.java
@@ -19,8 +19,11 @@ package org.apache.commons.jexl3;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
+
+import org.apache.commons.jexl3.internal.Debugger;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -36,6 +39,30 @@ public class ForEachTest extends JexlTestCase {
         super("ForEachTest");
     }
 
+    @Test public void testForLoop0b0() {
+        String src = "(l)->{ for(let x = 0, y = 0; x < 4; ++x) l.add(x) }";
+        JexlEngine jexl = new JexlBuilder().safe(false).create();
+        JexlScript script = jexl.createScript(src);
+        List<Integer> l = new ArrayList<>();
+        Object result = script.execute(null, l);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(Arrays.asList(0, 1, 2, 3), l);
+        String resrc = toString(script);
+        Assert.assertEquals(src, resrc);
+    }
+
+    @Test public void testForLoop0a() {
+        String src = "(l)->{ for(let x = 0; x < 4; ++x) { l.add(x); } }";
+        JexlEngine jexl = new JexlBuilder().safe(false).create();
+        JexlScript script = jexl.createScript(src);
+        List<Integer> l = new ArrayList<>();
+        Object result = script.execute(null, l);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(Arrays.asList(0, 1, 2, 3), l);
+        String resrc = toString(script);
+        Assert.assertEquals(src, resrc);
+    }
+
     @Test
     public void testForEachWithEmptyStatement() throws Exception {
         final JexlScript e = JEXL.createScript("for(item : list) ;");
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java 
b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index a8249556..68dafbfc 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -20,6 +20,7 @@ package org.apache.commons.jexl3;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 
+import org.apache.commons.jexl3.internal.Debugger;
 import org.apache.commons.jexl3.internal.OptionsContext;
 import org.apache.commons.jexl3.internal.Util;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
@@ -125,6 +126,12 @@ public class JexlTestCase {
         return arg.trim().replaceAll("\\s+", " ");
     }
 
+    public static String toString(JexlScript script) {
+        Debugger d = new Debugger().lineFeed("").indentation(0);
+        d.debug(script);
+        return  d.toString();
+    }
+
     /**
      * A very secure singleton.
      */
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java 
b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index e5faaef9..43d0fd9f 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -520,7 +520,7 @@ public class LexicalTest {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         final JexlScript script = jexl.createScript("let x = 32; (()->{ 
for(let x : null) { let c = 0; { return x; } } } )(); ");
         Assert.assertNotNull(script);
-        String dbg = script.getParsedText();
+        String dbg = JexlTestCase.toString(script);
         String src = script.getSourceText();
         Assert.assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbg));
     }
diff --git a/src/test/java/org/apache/commons/jexl3/SideEffectTest.java 
b/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
index a198165a..022f22d3 100644
--- a/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
@@ -23,12 +23,16 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.commons.jexl3.internal.introspection.NoJexlTest;
+import org.apache.commons.jexl3.jexl342.OptionalArithmetic;
 import org.apache.commons.jexl3.junit.Asserter;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 
@@ -363,70 +367,157 @@ public class SideEffectTest extends JexlTestCase {
         Object result;
         script = jexl.createScript("(x, y)->{ x += y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 + 15,  result);
+        Assert.assertEquals(3115 + 15, result);
         final Var v0 = new Var(3115);
         result = script.execute(jc, v0, new Var(15));
         Assert.assertEquals(result, v0);
-        Assert.assertEquals(3115 + 15,  v0.value);
+        Assert.assertEquals(3115 + 15, v0.value);
 
         script = jexl.createScript("(x, y)->{ x -= y}");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 - 15,  result);
+        Assert.assertEquals(3115 - 15, result);
         final Var v1 = new Var(3115);
         result = script.execute(jc, v1, new Var(15));
         Assert.assertNotEquals(result, v1); // not a real side effect
-        Assert.assertEquals(3115 - 15,  ((Var) result).value);
+        Assert.assertEquals(3115 - 15, ((Var) result).value);
 
         script = jexl.createScript("(x, y)->{ x *= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 * 15,  result);
+        Assert.assertEquals(3115 * 15, result);
         final Var v2 = new Var(3115);
         result = script.execute(jc, v2, new Var(15));
         Assert.assertEquals(result, v2);
-        Assert.assertEquals(3115 * 15,  v2.value);
+        Assert.assertEquals(3115 * 15, v2.value);
 
         script = jexl.createScript("(x, y)->{ x /= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 / 15,  result);
+        Assert.assertEquals(3115 / 15, result);
         final Var v3 = new Var(3115);
         result = script.execute(jc, v3, new Var(15));
         Assert.assertEquals(result, v3);
-        Assert.assertEquals(3115 / 15,  v3.value);
+        Assert.assertEquals(3115 / 15, v3.value);
 
         script = jexl.createScript("(x, y)->{ x %= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 % 15,  result);
+        Assert.assertEquals(3115 % 15, result);
         final Var v4 = new Var(3115);
         result = script.execute(jc, v4, new Var(15));
         Assert.assertEquals(result, v4);
-        Assert.assertEquals(3115 % 15,  v4.value);
+        Assert.assertEquals(3115 % 15, v4.value);
 
         script = jexl.createScript("(x, y)->{ x &= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L & 15,  result);
+        Assert.assertEquals(3115L & 15, result);
         final Var v5 = new Var(3115);
         result = script.execute(jc, v5, new Var(15));
         Assert.assertEquals(result, v5);
-        Assert.assertEquals(3115 & 15,  v5.value);
+        Assert.assertEquals(3115 & 15, v5.value);
 
         script = jexl.createScript("(x, y)->{ x |= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L | 15,  result);
+        Assert.assertEquals(3115L | 15, result);
         final Var v6 = new Var(3115);
         result = script.execute(jc, v6, new Var(15));
         Assert.assertEquals(result, v6);
-        Assert.assertEquals(3115L | 15,  v6.value);
+        Assert.assertEquals(3115L | 15, v6.value);
 
         script = jexl.createScript("(x, y)->{ x ^= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L ^ 15,  result);
+        Assert.assertEquals(3115L ^ 15, result);
         final Var v7 = new Var(3115);
         result = script.execute(jc, v7, new Var(15));
         Assert.assertEquals(result, v7);
-        Assert.assertEquals(3115L ^ 15,  v7.value);
+        Assert.assertEquals(3115L ^ 15, v7.value);
+
+        script = jexl.createScript("(x, y)->{ x >>>= y }");
+        result = script.execute(jc, 234453115, 5);
+        Assert.assertEquals(234453115L >>> 5, result);
+        final Var v8 = new Var(234453115);
+        result = script.execute(jc, v8, 5);
+        Assert.assertEquals(result, v8);
+        Assert.assertEquals(234453115L >>> 5, v8.value);
+
+        script = jexl.createScript("(x, y)->{ x >>= y }");
+        result = script.execute(jc, 435566788L, 7);
+        Assert.assertEquals(435566788L >> 7, result);
+        final Var v9 = new Var(435566788);
+        result = script.execute(jc, v9, 7);
+        Assert.assertEquals(result, v9);
+        Assert.assertEquals(435566788L >> 7, v9.value);
+
+        script = jexl.createScript("(x, y)->{ x <<= y }");
+        result = script.execute(jc, 3115, 2);
+        Assert.assertEquals(3115L << 2, result);
+        final Var v10 = new Var(3115);
+        result = script.execute(jc, v10, 2);
+        Assert.assertEquals(result, v10);
+        Assert.assertEquals(3115L << 2, v10.value);
     }
 
 
+    @Test
+    public void testIncrementSelf() throws Exception {
+        final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new 
SelfArithmetic(false)).create();
+        final JexlContext jc = null;
+        runSelfIncrement(jexl, jc);
+        runSelfIncrement(jexl, jc);
+    }
+
+    @Test
+    public void testIncrementSelfNoCache() throws Exception {
+        final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new 
SelfArithmetic(false)).create();
+        final JexlContext jc = null;
+        runSelfIncrement(jexl, jc);
+    }
+
+    protected void runSelfIncrement(final JexlEngine jexl, final JexlContext 
jc) {
+        JexlScript script = jexl.createScript("x -> [+x, +(x++), +x]");
+        final Var v11 = new Var(3115);
+        final AtomicInteger i11 = new AtomicInteger(3115);
+        for(Object v : Arrays.asList(v11, i11)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3115, r[0]);
+            Assert.assertEquals(3115, r[1]);
+            Assert.assertEquals(3116, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(++x), +x]");
+        final Var v12 = new Var(3189);
+        final AtomicInteger i12 = new AtomicInteger(3189);
+        for(Object v : Arrays.asList(v12, i12)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3189, r[0]);
+            Assert.assertEquals(3190, r[1]);
+            Assert.assertEquals(3190, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(x--), +x]");
+        final Var v13 = new Var(3115);
+        for(Object v : Arrays.asList(v13)) {
+            Object result = script.execute(jc, v13);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3115, r[0]);
+            Assert.assertEquals(3115, r[1]);
+            Assert.assertEquals(3114, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(--x), +x]");
+        final Var v14 = new Var(3189);
+        for(Object v : Arrays.asList(v14)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3189, r[0]);
+            Assert.assertEquals(3188, r[1]);
+            Assert.assertEquals(3188, r[2]);
+        }
+    }
+
     @Test
     public void testOverrideGetSet() throws Exception {
         final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new 
SelfArithmetic(false)).create();
@@ -456,14 +547,13 @@ public class SideEffectTest extends JexlTestCase {
             value = v;
         }
 
-        @Override
-        public String toString() {
+        @Override public String toString() {
             return Integer.toString(value);
         }
     }
 
     // an arithmetic that performs side effects
-    public static class SelfArithmetic extends JexlArithmetic {
+    public static class SelfArithmetic extends OptionalArithmetic {
         public SelfArithmetic(final boolean strict) {
             super(strict);
         }
@@ -484,9 +574,9 @@ public class SideEffectTest extends JexlTestCase {
             return "VALUE".equals(property)? var.value = v : 
JexlEngine.TRY_FAILED;
         }
 
-        public JexlOperator selfAdd(final Var lhs, final Var rhs) {
+        public Var selfAdd(final Var lhs, final Var rhs) {
             lhs.value += rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         // for kicks, this one does not side effect but overloads nevertheless
@@ -494,46 +584,97 @@ public class SideEffectTest extends JexlTestCase {
             return new Var(lhs.value - rhs.value);
         }
 
-        public JexlOperator selfDivide(final Var lhs, final Var rhs) {
+        public Var selfDivide(final Var lhs, final Var rhs) {
             lhs.value /= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
-        public JexlOperator selfMultiply(final Var lhs, final Var rhs) {
+        public Var selfMultiply(final Var lhs, final Var rhs) {
             lhs.value *= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
-        public JexlOperator selfMod(final Var lhs, final Var rhs) {
+        public Var selfMod(final Var lhs, final Var rhs) {
             lhs.value %= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var and(final Var lhs, final Var rhs) {
             return new Var(lhs.value & rhs.value);
         }
 
-        public JexlOperator selfAnd(final Var lhs, final Var rhs) {
+        public Var selfAnd(final Var lhs, final Var rhs) {
             lhs.value &= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var or(final Var lhs, final Var rhs) {
             return new Var(lhs.value | rhs.value);
         }
 
-        public JexlOperator selfOr(final Var lhs, final Var rhs) {
+        public Var selfOr(final Var lhs, final Var rhs) {
             lhs.value |= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var xor(final Var lhs, final Var rhs) {
             return new Var(lhs.value ^ rhs.value);
         }
 
-        public JexlOperator selfXor(final Var lhs, final Var rhs) {
+        public Var selfXor(final Var lhs, final Var rhs) {
             lhs.value ^= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
+        }
+
+        public Var shiftLeft(final Var lhs, final int rhs) {
+            return new Var(lhs.value << rhs);
+        }
+
+        public Var selfShiftLeft(final Var lhs, final int rhs) {
+            lhs.value <<= rhs;
+            return lhs;
+        }
+
+        public Var shiftRight(final Var lhs, final int rhs) {
+            return new Var(lhs.value >> rhs);
+        }
+
+        public Var selfShiftRight(final Var lhs, final int rhs) {
+            lhs.value >>= rhs;
+            return lhs;
+        }
+
+        public Var shiftRightUnsigned(final Var lhs, final int rhs) {
+            return new Var(lhs.value >>> rhs);
+        }
+
+        public Var selfShiftRightUnsigned(final Var lhs, final int rhs) {
+            lhs.value >>>= rhs;
+            return lhs;
+        }
+
+        public int increment(final Var lhs) {
+            return lhs.value + 1;
+        }
+
+        public int decrement(final Var lhs) {
+            return lhs.value - 1;
+        }
+
+        public int incrementAndGet(AtomicInteger i) {
+            return i.incrementAndGet();
+        }
+
+        public int getAndIncrement(AtomicInteger i) {
+            return i.getAndIncrement();
+        }
+
+        public int positivize(Var n) {
+            return n.value;
+        }
+
+        public int positivize(Number n) {
+            return n.intValue();
         }
     }
 
@@ -545,14 +686,14 @@ public class SideEffectTest extends JexlTestCase {
             super(astrict);
         }
 
-        public JexlOperator selfAdd(final Collection<String> c, final String 
item) throws IOException {
+        public Object selfAdd(final Collection<String> c, final String item) 
throws IOException {
             c.add(item);
-            return JexlOperator.ASSIGN;
+            return c;
         }
 
-        public JexlOperator selfAdd(final Appendable c, final String item) 
throws IOException {
+        public Object selfAdd(final Appendable c, final String item) throws 
IOException {
             c.append(item);
-            return JexlOperator.ASSIGN;
+            return c;
         }
 
         @Override
@@ -572,7 +713,7 @@ public class SideEffectTest extends JexlTestCase {
             }
             if (c instanceof Appendable) {
                 ((Appendable) c).append(item);
-                return JexlOperator.ASSIGN;
+                return c;
             }
             return JexlEngine.TRY_FAILED;
         }
diff --git a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java 
b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
index 2aaf29a9..8d5db9a5 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
@@ -18,8 +18,10 @@ package org.apache.commons.jexl3.junit;
 
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.BiPredicate;
 
 
 import org.apache.commons.jexl3.JexlEvalContext;
@@ -99,8 +101,11 @@ public class Asserter extends Assert {
             final JexlArithmetic jexla = engine.getArithmetic();
             Assert.assertEquals("expression: " + expression, 0,
                     ((BigDecimal) 
expected).compareTo(jexla.toBigDecimal(value)));
-        }
-        if (expected != null && value != null) {
+        } else if (expected instanceof BigInteger) {
+            final JexlArithmetic jexla = engine.getArithmetic();
+            Assert.assertEquals("expression: " + expression, 0,
+                    ((BigInteger) 
expected).compareTo(jexla.toBigInteger(value)));
+        } else if (expected != null && value != null) {
             if (expected.getClass().isArray() && value.getClass().isArray()) {
                 final int esz = Array.getLength(expected);
                 final int vsz = Array.getLength(value);
@@ -130,12 +135,15 @@ public class Asserter extends Assert {
      * @throws Exception if the expression did not fail or the exception did 
not match the expected pattern
      */
     public void failExpression(final String expression, final String 
matchException) throws Exception {
+         failExpression(expression, matchException, String::matches);
+    }
+    public void failExpression(final String expression, final String 
matchException, BiPredicate<String,String> predicate) throws Exception {
         try {
             final JexlScript exp = engine.createScript(expression);
             exp.execute(context);
             fail("expression: " + expression);
         } catch (final JexlException xjexl) {
-            if (matchException != null && 
!xjexl.getMessage().matches(matchException)) {
+            if (matchException != null && !predicate.test(xjexl.getMessage(), 
matchException)) {
                 fail("expression: " + expression + ", expected: " + 
matchException + ", got " + xjexl.getMessage());
             }
         }

Reply via email to