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

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

commit d8ea01c48e0053b69e79af6b0deebe3faaa8ee2d
Author: henrib <hen...@apache.org>
AuthorDate: Fri Jun 10 19:00:10 2022 +0200

    JEXL-372, JEXL-373: added support for 'for' and '++/--'
---
 RELEASE-NOTES.txt                                  |   3 +
 src/changes/changes.xml                            |   3 +
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  56 ++++++
 .../org/apache/commons/jexl3/JexlOperator.java     |  42 +++++
 .../apache/commons/jexl3/internal/Debugger.java    |  41 ++++
 .../apache/commons/jexl3/internal/Interpreter.java | 208 ++++++++++++++++-----
 .../apache/commons/jexl3/internal/Operators.java   |   8 +-
 .../commons/jexl3/internal/ScriptVisitor.java      |  12 ++
 .../commons/jexl3/parser/ASTForeachStatement.java  |  11 +-
 .../apache/commons/jexl3/parser/JexlParser.java    |   6 +-
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  53 +++++-
 .../apache/commons/jexl3/parser/ParserVisitor.java |  10 +
 .../commons/jexl3/ArithmeticOperatorTest.java      |  28 +++
 .../apache/commons/jexl3/ContextNamespaceTest.java |  54 +++---
 .../java/org/apache/commons/jexl3/DoWhileTest.java |  35 ++++
 .../org/apache/commons/jexl3/Issues300Test.java    |   1 -
 16 files changed, 484 insertions(+), 87 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index b56fde82..29e6ddee 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -38,6 +38,8 @@ allow fine-tuning the scripting integration into any project.
 
 New Features in 3.3:
 ====================
+* JEXL-373:     Add support for prefix/postfix increment/decrement operators
+* JEXL-372:     Add support for 'standard' for loop
 * JEXL-369:     Add 'let' and 'const' variable declarations
 * JEXL-365:     Lambda expressions
 * JEXL-363:     Allow retrieving captured variables in script
@@ -47,6 +49,7 @@ New Features in 3.3:
 
 Bugs Fixed in 3.3:
 ==================
+* JEXL-371:     Override of a protected method with public visibility is not 
callable
 * JEXL-370:     Cannot check if variable is defined using ObjectContext if the 
value is null
 * JEXL-364:     Evaluator options not propagated in closures
 * JEXL-362:     JexlInfo position reporting is off
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 573ee667..af5c9546 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
                 Configure accessible packages/classes/methods/fields
             </action>
             <!--  FIX -->
+            <action dev="henrib" type="fix" issue="JEXL-371">
+                Override of a protected method with public visibility is not 
callable
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-354"  due-to="William 
Price">
                 #pragma does not handle negative integer or real literals
             </action>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 75e77d16..b9236c3c 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -716,6 +716,62 @@ public class JexlArithmetic {
                         : null;
     }
 
+    /**
+     * Increments argument by 1.
+     * @param val the argument
+     * @return val + 1
+     */
+    public Object increment(Object val) {
+        return increment(val, 1);
+    }
+
+    /**
+     * Decrements argument by 1.
+     * @param val the argument
+     * @return val - 1
+     */
+    public Object decrement(Object val) {
+        return increment(val, -1);
+    }
+
+    /**
+     * Add value to number argument.
+     * @param val the number
+     * @param incr the value to add
+     * @return val + incr
+     */
+    protected Object increment(Object val, int incr) {
+        if (val == null) {
+            controlNullOperand();
+            return null;
+        }
+        if (val instanceof Integer) {
+            return ((Integer) val) + incr;
+        }
+        if (val instanceof Double) {
+            return ((Double) val) + incr;
+        }
+        if (val instanceof Long) {
+            return ((Long) val) + incr;
+        }
+        if (val instanceof BigDecimal) {
+            return ((BigDecimal) val).add(BigDecimal.valueOf(incr));
+        }
+        if (val instanceof BigInteger) {
+            return ((BigInteger) val).add(BigInteger.valueOf(incr));
+        }
+        if (val instanceof Float) {
+            return ((Float) val) + incr;
+        }
+        if (val instanceof Short) {
+            return (short) ((Short) val) + incr;
+        }
+        if (val instanceof Byte) {
+            return (byte) ((Byte) val) + incr;
+        }
+        throw new ArithmeticException("Object "+(incr < 0? 
"decrement":"increment")+":(" + val + ")");
+    }
+
     /**
      * Add two values together.
      * <p>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java 
b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 3a43d5b7..820ad0d0 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -317,6 +317,48 @@ public enum JexlOperator {
      */
     SELF_SHIFTLEFT("<<=", "selfShiftLeft", SHIFTLEFT),
 
+    /**
+     * Increment pseudo-operator.
+     * <br>No syntax, used as helper for <code>++</code>.
+     * @see JexlArithmetic#increment
+     */
+    INCREMENT("+1", "increment", 1),
+
+    /**
+     * Decrement pseudo-operator.
+     * <br>No syntax, used as helper for <code>--</code>.
+     * @see JexlArithmetic#decrement
+     */
+    DECREMENT("-1", "decrement", 1),
+
+    /**
+     * Prefix ++ operator, increments and returns the value after incrementing.
+     * <br><strong>Syntax:</strong> <code>++x</code>
+     * <br><strong>Method:</strong> <code>T incrementAndGet(L x);</code>.
+     */
+    INCREMENT_AND_GET("++.", "incrementAndGet", INCREMENT),
+
+    /**
+     * 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),
+
+    /**
+     * 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),
+
+    /**
+     * 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),
+
     /**
      * Marker for side effect.
      * <br>Returns this from 'self*' overload method to let the engine know 
the side effect has been performed and
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 ca3dacdb..1010f3f6 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -20,6 +20,7 @@ package org.apache.commons.jexl3.internal;
 import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.parser.*;
 
@@ -391,6 +392,26 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return data;
     }
 
+    /**
+     * Postfix operators.
+     * @param node a postfix operator
+     * @param prefix the postfix
+     * @param data visitor pattern argument
+     * @return visitor pattern value
+     */
+    protected Object postfixChild(final JexlNode node, final String prefix, 
final Object data) {
+        final boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
+        if (paren) {
+            builder.append('(');
+        }
+        accept(node.jjtGetChild(0), data);
+        if (paren) {
+            builder.append(')');
+        }
+        builder.append(prefix);
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTAddNode node, final Object data) {
         return additiveNode(node, " + ", data);
@@ -1110,6 +1131,26 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return infixChildren(node, " <<= ", false, data);
     }
 
+    @Override
+    protected Object visit(final ASTGetDecrementNode node, final Object data) {
+        return postfixChild(node, "++", data);
+    }
+
+    @Override
+    protected Object visit(final ASTGetIncrementNode node, final Object data) {
+        return postfixChild(node, "++", data);
+    }
+
+    @Override
+    protected Object visit(final ASTDecrementGetNode node, final Object data) {
+        return prefixChild(node, "--", data);
+    }
+
+    @Override
+    protected Object visit(final ASTIncrementGetNode node, final Object data) {
+        return prefixChild(node, "--", data);
+    }
+
     @Override
     protected Object visit(final ASTAnnotation node, final Object data) {
         final int num = node.jjtGetNumChildren();
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 98b01ea4..e316c854 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -624,6 +624,10 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(final ASTForeachStatement node, final Object data) {
+        return node.getLoopForm() == 0 ? forIterator(node, data) : 
forLoop(node, data);
+    }
+
+    protected Object forIterator(final ASTForeachStatement node, final Object 
data) {
         Object result = null;
         /* first objectNode is the loop variable */
         final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
@@ -645,44 +649,47 @@ public class Interpreter extends InterpreterBase {
             /* second objectNode is the variable to iterate */
             final Object iterableValue = node.jjtGetChild(1).jjtAccept(this, 
data);
             // make sure there is a value to iterate upon
-            if (iterableValue != null) {
-                /* third objectNode is the statement to execute */
-                final JexlNode statement = node.jjtGetNumChildren() >= 3 ? 
node.jjtGetChild(2) : null;
-                // get an iterator for the collection/array etc via the 
introspector.
-                forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, 
iterableValue);
-                final Iterator<?> itemsIterator = forEach instanceof Iterator
-                        ? (Iterator<?>) forEach
-                        : uberspect.getIterator(iterableValue);
-                if (itemsIterator != null) {
-                    int cnt = 0;
-                    while (itemsIterator.hasNext()) {
-                        cancelCheck(node);
-                        // reset loop variable
-                        if (lexical && cnt++ > 0) {
-                            // clean up but remain current
-                            block.pop();
-                            // unlikely to fail
-                            if (loopSymbol && !defineVariable((ASTVar) 
loopVariable, locals)) {
-                                return redefinedVariable(node, 
loopVariable.getName());
-                            }
-                        }
-                        // set loopVariable to value of iterator
-                        final Object value = itemsIterator.next();
-                        if (symbol < 0) {
-                            setContextVariable(node, loopVariable.getName(), 
value);
-                        } else {
-                            frame.set(symbol, value);
-                        }
-                        if (statement != null) {
-                            try {
-                                // execute statement
-                                result = statement.jjtAccept(this, data);
-                            } catch (final JexlException.Break stmtBreak) {
-                                break;
-                            } catch (final JexlException.Continue 
stmtContinue) {
-                                //continue;
-                            }
-                        }
+            if (iterableValue == null) {
+                return null;
+            }
+            /* last child node is the statement to execute */
+            int numChildren = node.jjtGetNumChildren();
+            final JexlNode statement = numChildren >= 3 ? 
node.jjtGetChild(numChildren - 1) : null;
+            // get an iterator for the collection/array etc via the 
introspector.
+            forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, 
iterableValue);
+            final Iterator<?> itemsIterator = forEach instanceof Iterator
+                    ? (Iterator<?>) forEach
+                    : uberspect.getIterator(iterableValue);
+            if (itemsIterator == null) {
+                return null;
+            }
+            int cnt = 0;
+            while (itemsIterator.hasNext()) {
+                cancelCheck(node);
+                // reset loop variable
+                if (lexical && cnt++ > 0) {
+                    // clean up but remain current
+                    block.pop();
+                    // unlikely to fail
+                    if (loopSymbol && !defineVariable((ASTVar) loopVariable, 
locals)) {
+                        return redefinedVariable(node, loopVariable.getName());
+                    }
+                }
+                // set loopVariable to value of iterator
+                final Object value = itemsIterator.next();
+                if (symbol < 0) {
+                    setContextVariable(node, loopVariable.getName(), value);
+                } else {
+                    frame.set(symbol, value);
+                }
+                if (statement != null) {
+                    try {
+                        // execute statement
+                        result = statement.jjtAccept(this, data);
+                    } catch (final JexlException.Break stmtBreak) {
+                        break;
+                    } catch (final JexlException.Continue stmtContinue) {
+                        //continue;
                     }
                 }
             }
@@ -697,6 +704,80 @@ public class Interpreter extends InterpreterBase {
         return result;
     }
 
+    protected Object forLoop(final ASTForeachStatement node, final Object 
data) {
+        Object result = null;
+        int nc;
+        int form = node.getLoopForm();
+        final LexicalFrame locals;
+        /* first child node might be the loop variable */
+        if ((form & 1) != 0) {
+            nc = 1;
+            JexlNode init = node.jjtGetChild(0);
+            ASTVar loopVariable = null;
+            if (init instanceof ASTAssignment) {
+                JexlNode child = init.jjtGetChild(0);
+                if (child instanceof ASTVar) {
+                    loopVariable = (ASTVar) child;
+                }
+            } else if (init instanceof  ASTVar){
+                loopVariable = (ASTVar) init;
+            }
+            if (loopVariable != null) {
+                final boolean lexical = loopVariable.isLexical() || 
options.isLexical();
+                locals = lexical ? new LexicalFrame(frame, block) : null;
+                if (locals != null) {
+                    block = locals;
+                }
+            } else {
+                locals = null;
+            }
+            // initialize after eventual creation of local lexical frame
+            init.jjtAccept(this, data);
+            nc = 1;
+        } 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;
+            while (predicate == null || 
arithmetic.toBoolean(predicate.jjtAccept(this, data))) {
+                cancelCheck(node);
+                // the body
+                if (statement != null) {
+                    try {
+                        // execute statement
+                        result = statement.jjtAccept(this, data);
+                    } catch (final JexlException.Break stmtBreak) {
+                        break;
+                    } catch (final JexlException.Continue stmtContinue) {
+                        //continue;
+                    }
+                }
+                // the step
+                if (step != null) {
+                    step.jjtAccept(this, data);
+                }
+            }
+        } finally {
+            //  closeable iterator handling
+            closeIfSupported(forEach);
+            // restore lexical frame
+            if (locals != null) {
+                block = block.pop();
+            }
+        }
+        return result;
+    }
+
     @Override
     protected Object visit(final ASTWhileStatement node, final Object data) {
         Object result = null;
@@ -1267,6 +1348,35 @@ public class Interpreter extends InterpreterBase {
         return executeAssign(node, JexlOperator.SELF_SHIFTRIGHTU, data);
     }
 
+    @Override
+    protected Object visit(final ASTGetDecrementNode node, final Object data) {
+        return executeAssign(node, JexlOperator.GET_AND_DECREMENT, data);
+    }
+
+    @Override
+    protected Object visit(final ASTGetIncrementNode node, final Object data) {
+        return executeAssign(node, JexlOperator.GET_AND_INCREMENT, data);
+    }
+
+    @Override
+    protected Object visit(final ASTDecrementGetNode node, final Object data) {
+        return executeAssign(node, JexlOperator.DECREMENT_AND_GET, data);
+    }
+
+    @Override
+    protected Object visit(final ASTIncrementGetNode node, final Object data) {
+        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--)
+     */
+    static private 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
@@ -1299,14 +1409,16 @@ public class Interpreter extends InterpreterBase {
         // 0: determine initial object & property:
         final int last = left.jjtGetNumChildren() - 1;
         // right is the value expression to assign
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        Object right = node.jjtGetNumChildren() < 2? null: 
node.jjtGetChild(1).jjtAccept(this, data);
+        // previous value for postfix assignment operators (x++, x--)
+        Object actual = null;
         // 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 = getVariable(frame, block, var);
+                        final Object self = actual = getVariable(frame, block, 
var);
                         right = operators.tryAssignOverload(node, assignop, 
self, right);
                         if (right == JexlOperator.ASSIGN) {
                             return self;
@@ -1317,7 +1429,7 @@ public class Interpreter extends InterpreterBase {
                     if (right instanceof Closure) {
                         ((Closure) right).setCaptured(symbol, right);
                     }
-                    return right; // 1
+                    return isPostfix(assignop)? actual : right; // 1
                 }
                 object = getVariable(frame, block, var);
                 // top level is a symbol, can not be an antish var
@@ -1326,14 +1438,14 @@ public class Interpreter extends InterpreterBase {
                 // check we are not assigning direct global
                 if (last < 0) {
                     if (assignop != null) {
-                        final Object self = context.get(var.getName());
+                        final Object self = actual = 
context.get(var.getName());
                         right = operators.tryAssignOverload(node, assignop, 
self, right);
                         if (right == JexlOperator.ASSIGN) {
                             return self;
                         }
                     }
                     setContextVariable(node, var.getName(), right);
-                    return right; // 2
+                    return isPostfix(assignop)? actual : right; // 2
                 }
                 object = context.get(var.getName());
                 // top level accesses object, can not be an antish var
@@ -1389,7 +1501,7 @@ public class Interpreter extends InterpreterBase {
                 throw new JexlException(objectNode, "illegal assignment form");
             }
         }
-        // 2: last objectNode will perform assignement in all cases
+        // 2: last objectNode will perform assignment in all cases
         JexlNode propertyNode = left.jjtGetChild(last);
         final ASTIdentifierAccess propertyId = propertyNode instanceof 
ASTIdentifierAccess
                 ? (ASTIdentifierAccess) propertyNode
@@ -1403,14 +1515,14 @@ public class Interpreter extends InterpreterBase {
                 }
                 ant.append(propertyId.getName());
                 if (assignop != null) {
-                    final Object self = context.get(ant.toString());
+                    final Object self = actual = context.get(ant.toString());
                     right = operators.tryAssignOverload(node, assignop, self, 
right);
                     if (right == JexlOperator.ASSIGN) {
                         return self;
                     }
                 }
                 setContextVariable(propertyNode, ant.toString(), right);
-                return right; // 3
+                return isPostfix(assignop)? actual : right; // 3
             }
             // property of an object ?
             property = evalIdentifier(propertyId);
@@ -1435,14 +1547,14 @@ public class Interpreter extends InterpreterBase {
         }
         // 3: one before last, assign
         if (assignop != null) {
-            final Object self = getAttribute(object, property, propertyNode);
+            final Object self = actual = getAttribute(object, property, 
propertyNode);
             right = operators.tryAssignOverload(node, assignop, self, right);
             if (right == JexlOperator.ASSIGN) {
                 return self;
             }
         }
         setAttribute(object, property, right, propertyNode);
-        return right; // 4
+        return isPostfix(assignop)? actual : right; // 4
     }
 
     @Override
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 e26d1ea3..aa548e0e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -141,7 +141,7 @@ public class Operators {
      */
     protected Object tryAssignOverload(final JexlNode node, final JexlOperator 
operator, final Object...args) {
         final JexlArithmetic arithmetic = interpreter.arithmetic;
-        if (args.length != operator.getArity()) {
+        if (args.length < operator.getArity()) {
             return JexlEngine.TRY_FAILED;
         }
         // try to call overload with side effect
@@ -193,6 +193,12 @@ public class Operators {
                     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());
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 bdcd51e4..5964d348 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -436,6 +436,18 @@ public class ScriptVisitor extends ParserVisitor {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(ASTGetDecrementNode node, Object data) { return 
visitNode(node, data); }
+
+    @Override
+    protected Object visit(ASTGetIncrementNode node, Object data) { return 
visitNode(node, data); }
+
+    @Override
+    protected Object visit(ASTDecrementGetNode node, Object data) { return 
visitNode(node, data); }
+
+    @Override
+    protected Object visit(ASTIncrementGetNode node, Object data) { return 
visitNode(node, data); }
+
     @Override
     protected Object visit(final ASTSetShiftRightNode node, final Object data) 
{
         return visitNode(node, data);
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
index a82357c3..200371d2 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
@@ -20,6 +20,15 @@ package org.apache.commons.jexl3.parser;
  * Declares a for each loop.
  */
 public class ASTForeachStatement extends JexlLexicalNode {
+    private int loopForm;
+
+    void setLoopForm(int form) {
+        loopForm = form;
+    }
+
+    public int getLoopForm() {
+        return loopForm;
+    }
 
     public ASTForeachStatement(final int id) {
         super(id);
@@ -34,4 +43,4 @@ public class ASTForeachStatement extends JexlLexicalNode {
         return visitor.visit(this, data);
     }
 
-}
\ No newline at end of file
+}
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 3f0ff1ba..b5db9195 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -578,7 +578,11 @@ public abstract class JexlParser extends StringParser {
             ASTSetXorNode.class,
             ASTSetShiftLeftNode.class,
             ASTSetShiftRightNode.class,
-            ASTSetShiftRightUnsignedNode.class
+            ASTSetShiftRightUnsignedNode.class,
+            ASTIncrementGetNode.class,
+            ASTDecrementGetNode.class,
+            ASTGetDecrementNode.class,
+            ASTGetIncrementNode.class
         )
     );
 
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 865ccc8c..f8072a9f 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -204,7 +204,9 @@ TOKEN_MGR_DECLS : {
 
     | < assign : "=" >
     | < plus : "+" >
+    | < plusplus : "++" >
     | < minus : "-" >
+    | < minusminus : "--" >
     | < mult : "*" >
     | < div : "/" >
     | < DIV : "div" > { popDot(); }
@@ -426,13 +428,31 @@ void Break() #Break : {
     t=<BREAK> { if (loopCount == 0) { throwParsingException(t); } }
 }
 
-void ForeachStatement() : {}
+void ForeachStatement() : {
+ int loopForm = 0;
+}
 {
-    { pushUnit(jjtThis); }
-    <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN>
-        { loopCount += 1; }
-        (LOOKAHEAD(1) Block() | StatementNoVar())
-    { loopCount -= 1; popUnit(jjtThis); }
+    {
+        pushUnit(jjtThis);
+    }
+    <FOR> <LPAREN>
+    (
+        LOOKAHEAD(3) ForEachVar() <COLON> Expression() { loopForm = 0; }
+    |
+        ((LOOKAHEAD(1) Var() | Expression()) { loopForm = 1; })? <SEMICOL>
+        (Expression() { loopForm |= 2; })? <SEMICOL>
+        (Expression() { loopForm |= 4; })? { loopForm |= 8; }
+    )
+    <RPAREN>
+    {
+        loopCount += 1;
+    }
+        (LOOKAHEAD(1) Block() | StatementNoVar() )
+    {
+        loopCount -= 1;
+        jjtThis.setLoopForm(loopForm);
+        popUnit(jjtThis);
+    }
 }
 
 void ForEachVar() #Reference : {}
@@ -563,7 +583,7 @@ void ConditionalExpression() #void : {}
     <ELVIS> Expression() #TernaryNode(2)
   |
     <NULLP> Expression() #NullpNode(2)
-  ) )?
+  ) )*
 }
 
 void ConditionalOrExpression() #void : {}
@@ -685,7 +705,23 @@ void UnaryExpression() #void : {}
   |
     <SIZE> UnaryExpression() #SizeFunction(1)
   |
-    ValueExpression()
+   <minusminus> UnaryExpression() #DecrementGetNode(1)
+  |
+   <plusplus> UnaryExpression() #IncrementGetNode(1)
+  |
+    PostfixExpression()
+}
+
+void PostfixOperator() #void : {}
+{
+    <plusplus> #GetIncrementNode(1)
+  |
+    <minusminus> #GetDecrementNode(1)
+}
+
+void PostfixExpression() #void : {}
+{
+   ValueExpression() [ LOOKAHEAD(1) PostfixOperator() ]
 }
 
 /***************************************
@@ -1000,7 +1036,6 @@ void MethodCall() #void : {}
     (MemberAccess() (LOOKAHEAD(<LPAREN>) Arguments())+) #MethodNode(>1)
 }
 
-
 void MemberExpression() #void : {}
 {
     LOOKAHEAD(MethodCall()) MethodCall() | MemberAccess()
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 a6560225..f03d4a99 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.jexl3.parser;
 
+import org.apache.commons.jexl3.JexlOperator;
+
 /**
  * Fully abstract to avoid public interface exposition.
  */
@@ -192,6 +194,14 @@ 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(final ASTGetIncrementNode node, final 
Object data);
+
+    protected abstract Object visit(final ASTDecrementGetNode node, final 
Object data);
+
+    protected abstract Object visit(final ASTIncrementGetNode node, final 
Object data);
+
     protected abstract Object visit(ASTJxltLiteral node, Object data);
 
     protected abstract Object visit(ASTAnnotation node, Object data);
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index 9bcaf62c..ab7db95b 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -557,4 +557,32 @@ public class ArithmeticOperatorTest extends JexlTestCase {
         strws = strw.toString();
         Assert.assertNotNull(strws);
     }
+
+    @Test public void test373a() {
+        testSelfAssignOperators("y.add(x++)", 42, 42, 43);
+    }
+    @Test public void test373b() {
+        testSelfAssignOperators("y.add(++x)", 42, 43, 43);
+    }
+    @Test public void test373c() {
+        testSelfAssignOperators("y.add(x--)", 42, 42, 41);
+    }
+    @Test public void test373d() {
+        testSelfAssignOperators("y.add(--x)", 42, 41, 41);
+    }
+
+    void testSelfAssignOperators(String text, int x, int y0, int x0) {
+        //String text = "y.add(x++)";
+        JexlEngine jexl = new JexlBuilder().safe(true).create();
+        JexlScript script = jexl.createScript(text);
+        JexlContext context = new MapContext();
+        context.set("x", x);
+        List<Number> y = new ArrayList<>();
+        context.set("y", y);
+        Object result = script.execute(context);
+        Assert.assertEquals("x0", x0, context.get("x"));
+        Assert.assertEquals("y0", y0, y.get(0));
+    }
+
+
 }
diff --git a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java 
b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
index ad8d9c1d..5b73cf77 100644
--- a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
@@ -17,20 +17,17 @@
 package org.apache.commons.jexl3;
 
 import org.junit.Assert;
-import org.junit.Ignore;
 import org.junit.Test;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Tests JexlContext (advanced) features.
  */
-@SuppressWarnings({"UnnecessaryBoxing", 
"AssertEqualsBetweenInconvertibleTypes"})
+@SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
 public class ContextNamespaceTest extends JexlTestCase {
 
     public ContextNamespaceTest() {
@@ -84,9 +81,9 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testThreadedContext() throws Exception {
+    public void testThreadedContext() {
         final JexlEngine jexl = new JexlBuilder().create();
-        final TaxesContext context = new TaxesContext(18.6);
+        final JexlContext context = new TaxesContext(18.6);
         final String strs = "taxes:vat(1000)";
         final JexlScript staxes = jexl.createScript(strs);
         final Object result = staxes.execute(context);
@@ -94,7 +91,7 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespacePragma() throws Exception {
+    public void testNamespacePragma() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext context = new TaxesContext(18.6);
         // local namespace tax declared
@@ -111,7 +108,7 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace346a() throws Exception {
+    public void testNamespace346a() {
         JexlContext ctxt = new Context346();
         final JexlEngine jexl = new JexlBuilder().safe(false).create();
         String src = "x != null ? x : func(y)";
@@ -123,9 +120,9 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace346b() throws Exception {
+    public void testNamespace346b() {
         JexlContext ctxt = new MapContext();
-        Map<String, Object> ns = new HashMap<String, Object>();
+        Map<String, Object> ns = new HashMap<>();
         ns.put("x", Math.class);
         ns.put(null, Math.class);
         final JexlEngine jexl = new 
JexlBuilder().safe(false).namespaces(ns).create();
@@ -151,9 +148,9 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace348a() throws Exception {
+    public void testNamespace348a() {
         JexlContext ctxt = new MapContext();
-        Map<String, Object> ns = new HashMap<String, Object>();
+        Map<String, Object> ns = new HashMap<>();
         ns.put("ns", Ns348.class);
         final JexlEngine jexl = new 
JexlBuilder().safe(false).namespaces(ns).create();
         run348a(jexl, ctxt);
@@ -163,7 +160,7 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace348b() throws Exception {
+    public void testNamespace348b() {
         JexlContext ctxt = new ContextNs348();
         final JexlEngine jexl = new JexlBuilder().safe(false).create();
         // no space for ns name as syntactic hint
@@ -174,9 +171,9 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace348c() throws Exception {
+    public void testNamespace348c() {
         JexlContext ctxt = new ContextNs348();
-        Map<String, Object> ns = new HashMap<String, Object>();
+        Map<String, Object> ns = new HashMap<>();
         ns.put("ns", Ns348.class);
         JexlFeatures f = new JexlFeatures();
         f.namespaceTest((n)->true);
@@ -188,7 +185,7 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNamespace348d() throws Exception {
+    public void testNamespace348d() {
         JexlContext ctxt = new ContextNs348();
         JexlFeatures f = new JexlFeatures();
         f.namespaceTest((n)->true);
@@ -240,9 +237,9 @@ public class ContextNamespaceTest extends JexlTestCase {
         // local vars
         JexlScript script = jexl.createScript(src, "x", "z", "y");
         Object result = script.execute(ctxt, null, 169, 1);
-        Assert.assertEquals(169, result);
+        Assert.assertEquals(src, 169, result);
         result = script.execute(ctxt, "42", 169, 1);
-        Assert.assertEquals(42, result);
+        Assert.assertEquals(src, 42, result);
     }
 
     private void run348d(JexlEngine jexl, JexlContext ctxt) {
@@ -251,19 +248,24 @@ public class ContextNamespaceTest extends JexlTestCase {
     private void run348d(JexlEngine jexl, JexlContext ctxt, String ns) {
         String src = "empty(x) ? z : "+ns+"func(y)";
         // global vars
-        JexlScript script = jexl.createScript(src);
+        JexlScript script = null;
+        try {
+           script = jexl.createScript(src);
+        } catch(JexlException.Parsing xparse) {
+            Assert.fail(src);
+        }
         ctxt.set("x", null);
         ctxt.set("z", 169);
         ctxt.set("y", 1);
         Object result = script.execute(ctxt);
-        Assert.assertEquals(169, result);
+        Assert.assertEquals(src, 169, result);
         ctxt.set("x", "42");
         result = script.execute(ctxt);
-        Assert.assertEquals(42, result);
+        Assert.assertEquals(src,42, result);
     }
 
     @Test
-    public void testNamespacePragmaString() throws Exception {
+    public void testNamespacePragmaString() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext context = new MapContext();
         // local namespace str declared
@@ -300,10 +302,10 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testObjectContext() throws Exception {
+    public void testObjectContext() {
         final JexlEngine jexl = new 
JexlBuilder().strict(true).silent(false).create();
         final Vat vat = new Vat(18.6);
-        final ObjectContext<Vat> ctxt = new ObjectContext<Vat>(jexl, vat);
+        final ObjectContext<Vat> ctxt = new ObjectContext<>(jexl, vat);
         Assert.assertEquals(18.6d, (Double) ctxt.get("VAT"), 0.0001d);
         ctxt.set("VAT", 20.0d);
         Assert.assertEquals(20.0d, (Double) ctxt.get("VAT"), 0.0001d);
@@ -339,14 +341,14 @@ public class ContextNamespaceTest extends JexlTestCase {
     }
 
     @Test
-    public void testNsNsContext0() throws Exception {
+    public void testNsNsContext0() {
         nsnsCtor.set(0);
         String clsName = NsNs.class.getName();
         runNsNsContext(Collections.singletonMap("nsns", clsName));
     }
 
     @Test
-    public void testNsNsContext1() throws Exception {
+    public void testNsNsContext1() {
         nsnsCtor.set(0);
         runNsNsContext(Collections.singletonMap("nsns", NsNs.class));
     }
diff --git a/src/test/java/org/apache/commons/jexl3/DoWhileTest.java 
b/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
index 3756529d..3f2e43f9 100644
--- a/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
+++ b/src/test/java/org/apache/commons/jexl3/DoWhileTest.java
@@ -19,6 +19,10 @@ package org.apache.commons.jexl3;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests do while statement.
  * @since 3.2
@@ -137,4 +141,35 @@ public class DoWhileTest extends JexlTestCase {
         final Object o = e.execute(jc);
         Assert.assertEquals(10, o);
     }
+
+    @Test public void testForLoop0() {
+        String src = "(l)->{ for(let x = 0; x < 4; ++x) { l.add(x); } }";
+        JexlEngine jexl = new JexlBuilder().safe(true).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);
+    }
+
+    @Test public void testForLoop1() {
+        String src = "(l)->{ for(var x = 0; x < 4; ++x) { l.add(x); } }";
+        JexlEngine jexl = new JexlBuilder().safe(true).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);
+    }
+
+    @Test public void testForLoop2() {
+        String src = "(l)->{ for(x = 0; x < 4; ++x) { l.add(x); } }";
+        JexlEngine jexl = new JexlBuilder().safe(true).create();
+        JexlScript script = jexl.createScript(src);
+        List<Integer> l = new ArrayList<>();
+        JexlContext ctxt = new MapContext();
+        Object result = script.execute(ctxt, l);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(Arrays.asList(0, 1, 2, 3), l);
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index e48ebf8e..4c1eb00f 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -28,7 +28,6 @@ import java.util.Map;
 
 import org.apache.commons.jexl3.internal.Engine32;
 import org.apache.commons.jexl3.internal.OptionsContext;
-import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.junit.Assert;
 import static org.junit.Assert.assertEquals;
 import org.junit.Test;

Reply via email to