This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-440 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/JEXL-440 by this push: new e9375c97 JEXL-440 : basic switch statement & expression tests added; e9375c97 is described below commit e9375c974926c19f328c8c39aac9e1f6531a5105 Author: Henrib <hbies...@gmail.com> AuthorDate: Sun Jun 8 17:17:42 2025 +0200 JEXL-440 : basic switch statement & expression tests added; --- .../org/apache/commons/jexl3/JexlFeatures.java | 21 +++++++++- .../apache/commons/jexl3/internal/Interpreter.java | 48 ++++++++++++++++------ .../commons/jexl3/parser/ASTSwitchStatement.java | 4 +- .../org/apache/commons/jexl3/parser/Parser.jjt | 6 +-- .../org/apache/commons/jexl3/Issues400Test.java | 26 +++++++++++- 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index d2cb4dbe..65b7fced 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -172,8 +172,7 @@ public final class JexlFeatures { * @since 3.3.1 */ private static final Set<String> RESERVED_WORDS = - Collections.unmodifiableSet( - new HashSet<>(Arrays.asList( "class", "jexl", "$jexl"))); + Collections.unmodifiableSet(new HashSet<>(Arrays.asList( "class", "jexl", "$jexl"))); /* * *WARNING* @@ -718,10 +717,28 @@ public final class JexlFeatures { } } + /** + * Sets whether statements can be ambiguous. + * <p> + * When enabled, the semicolumn is not required between expressions that otherwise are considered + * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this + * may be missing an operator or that the intent is not clear. + * </p> + * @param flag true to enable, false to disable + */ public void setAmbiguousStatement(final boolean flag) { setFeature(AMBIGUOUS_STATEMENT, flag); } + /** + * Checks whether statements can be ambiguous. + * <p> + * When enabled, the semicolumn is not required between expressions that otherwise are considered + * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this + * may be missing an operator or that the intent is not clear. + * </p> + * @return true if statements can be ambiguous, false otherwise + */ public boolean supportsAmbiguousStatement() { return getFeature(AMBIGUOUS_STATEMENT); } 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 dc097f99..decbc26e 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -1230,24 +1230,19 @@ public class Interpreter extends InterpreterBase { } } - @Override - protected Object visit(ASTSwitchStatement node, Object data) { - return null; - } - @Override protected Object visit(ASTCaseStatement node, Object data) { - return null; - } - - @Override - protected Object visit(ASTSwitchExpression node, Object data) { - return null; + final int argc = node.jjtGetNumChildren(); + Object result = null; + for (int i = 0; i < argc; i++) { + result = node.jjtGetChild(i).jjtAccept(this, data); + } + return result; } @Override protected Object visit(ASTCaseExpression node, Object data) { - return null; + return node.jjtGetChild(0).jjtAccept(this, data); } @Override @@ -2026,6 +2021,35 @@ public class Interpreter extends InterpreterBase { return operators.startsWith(node, JexlOperator.STARTSWITH, left, right); } + + @Override + protected Object visit(final ASTSwitchExpression node, final Object data) { + final Object value = node.jjtGetChild(0).jjtAccept(this, data); + final int index = node.switchIndex(value); + return index >= 0 ? node.jjtGetChild(index).jjtAccept(this, data) : null; + } + + @Override + protected Object visit(final ASTSwitchStatement node, final Object data) { + final int count = node.jjtGetNumChildren(); + Object value = node.jjtGetChild(0).jjtAccept(this, data); + int index = node.switchIndex(value); + if (index > 0) { + for (int i = index; i < count; ++i) { + try { + // evaluate the switch body + value = node.jjtGetChild(i).jjtAccept(this, data); + } catch (final JexlException.Break xbreak) { + break; // break out of the switch + } catch (final JexlException.Continue xcontinue) { + // continue to next case + } + } + return value; + } + return null; + } + @Override protected Object visit(final ASTTernaryNode node, final Object data) { Object condition; diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java index 40f966be..6ea12992 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java @@ -80,7 +80,7 @@ public class ASTSwitchStatement extends JexlNode { if (index == null) { index = cases.get(JexlParser.DFLT); } - if (index != null && index >= 0 && index < jjtGetNumChildren()) { + if (index != null && index >= 1 && index < jjtGetNumChildren()) { return index; // index is 1-based, children are 0-based } return -1; @@ -91,7 +91,7 @@ public class ASTSwitchStatement extends JexlNode { * <p>It detects duplicates cases and default.</p> */ public static class Helper { - private int nswitch = 0; + private int nswitch = 1; // switch index, starts at 1 since the first child is the switch expression private boolean defaultDefined = false; private final Map<Object, Integer> dispatch = new LinkedHashMap<>(); 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 397be2d9..caed1e89 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt +++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt @@ -440,7 +440,7 @@ void FunctionStatement() #JexlLambda : {} void ExpressionStatement() #void : {} { - Expression() (LOOKAHEAD(Expression()/*, { getFeatures().supportsAmbiguousStatement() }*/) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)? + Expression() (LOOKAHEAD(Expression(), { !getFeatures().supportsAmbiguousStatement() } ) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)? } @@ -611,8 +611,8 @@ void SwitchStatementCase(SwitchSet cases) #CaseStatement : loopCount += 1; } { - ( ( LOOKAHEAD(2) <CASE> constant=constLiteral() <COLON> )+ - { constants = Collections.singletonList(constant); jjtThis.setValues(constants); cases.addAll(constants); } + ( ( LOOKAHEAD(2) <CASE> constant=constLiteral() <COLON> + { constants = Collections.singletonList(constant); jjtThis.setValues(constants); cases.addAll(constants); } )+ | <CASE_DEFAULT> <COLON> ) ( LOOKAHEAD(<LCURLY>) Block() | (StatementNoVar())+ )? diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 426e1009..8a969b2d 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -733,11 +733,22 @@ public class Issues400Test { f.setAmbiguousStatement(true); JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create(); String src = -"let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\ndefault -> { let z = 4; z + 6 } } y"; +"let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\ndefault -> { let z = 4; z + x } } y"; JexlScript script = jexl.createScript(src, "x"); assertNotNull(script); String dbgStr = script.getParsedText(); assertNotNull(dbgStr); + + Object result = script.execute(null, 10); + Assertions.assertEquals(3, result); + result = script.execute(null, 11); + Assertions.assertEquals(3, result); + result = script.execute(null, 20); + Assertions.assertEquals(4, result); + result = script.execute(null, 21); + Assertions.assertEquals(4, result); + result = script.execute(null, 38); + Assertions.assertEquals(42, result); src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4 } y"; try { @@ -752,10 +763,21 @@ public class Issues400Test { void test440b() { JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create(); final String src = -"switch (x) { case 10 : 3; case 20 : case 21 : 4; case 32: break; default : return 5; } "; +"switch (x) { case 10 : return 3; case 20 : case 21 : return 4; case 32: break; default : return x + 4; } 169"; final JexlScript script = jexl.createScript(src, "x"); assertNotNull(script); String dbgStr = script.getParsedText(); assertNotNull(dbgStr); + + Object result = script.execute(null, 10); + Assertions.assertEquals(3, result); + result = script.execute(null, 20); + Assertions.assertEquals(4, result); + result = script.execute(null, 21); + Assertions.assertEquals(4, result); + result = script.execute(null, 32); + Assertions.assertEquals(169, result); + result = script.execute(null, 38); + Assertions.assertEquals(42, result); } }