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;