You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/06/10 17:00:20 UTC

[commons-jexl] branch JEXL-37_23 created (now d8ea01c4)

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

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


      at d8ea01c4 JEXL-372, JEXL-373: added support for 'for' and '++/--'

This branch includes the following new commits:

     new d8ea01c4 JEXL-372, JEXL-373: added support for 'for' and '++/--'

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[commons-jexl] 01/01: JEXL-372, JEXL-373: added support for 'for' and '++/--'

Posted by he...@apache.org.
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 <he...@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;