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

commit c224a05a0a75b05dc19c965519d8a6424e55d4cf
Author: Henrib <hbies...@gmail.com>
AuthorDate: Fri Jun 6 21:17:27 2025 +0200

    JEXL-440 : added switch/case/default syntax for statement and expressions;
    - Debugger handling of new AST nodes;
---
 .../org/apache/commons/jexl3/JexlFeatures.java     |  21 ++-
 .../apache/commons/jexl3/internal/Debugger.java    | 188 ++++++++++-----------
 .../apache/commons/jexl3/internal/Interpreter.java | 116 +++----------
 .../commons/jexl3/internal/ScriptVisitor.java      | 115 +++----------
 .../commons/jexl3/parser/ASTCaseExpression.java    |  30 ++++
 .../commons/jexl3/parser/ASTCaseStatement.java     |  57 +++++++
 .../commons/jexl3/parser/ASTSwitchExpression.java  |  33 ++++
 .../commons/jexl3/parser/ASTSwitchStatement.java   | 115 +++++++++++++
 .../apache/commons/jexl3/parser/JexlParser.java    | 120 ++++++++++++-
 .../apache/commons/jexl3/parser/NumberParser.java  |  14 ++
 .../org/apache/commons/jexl3/parser/Parser.jjt     | 111 +++++++++++-
 .../apache/commons/jexl3/parser/ParserVisitor.java |   8 +
 .../org/apache/commons/jexl3/FeaturesTest.java     |   6 +-
 .../org/apache/commons/jexl3/Issues400Test.java    |  26 ++-
 .../java/org/apache/commons/jexl3/LambdaTest.java  |  26 ++-
 15 files changed, 664 insertions(+), 322 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 3d933e13..d2cb4dbe 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -73,7 +73,7 @@ public final class JexlFeatures {
         "global assign/modify", "array reference", "create instance", "loop", 
"function",
         "method call", "set/map/array literal", "pragma", "annotation", 
"script", "lexical", "lexicalShade",
         "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", 
"import pragma", "comparator names", "pragma anywhere",
-        "const capture", "ref capture"
+        "const capture", "ref capture", "ambiguous statement"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -125,13 +125,13 @@ public final class JexlFeatures {
     public static final int CONST_CAPTURE = 23;
     /** Captured variables are reference. */
     public static final int REF_CAPTURE = 24;
-    /** Captured variables are reference. */
-    public static final int STRICT_STATEMENT = 25;
+    /** Ambiguous or strict statement allowed. */
+    public static final int AMBIGUOUS_STATEMENT = 25;
     /**
      * All features.
      * Ensure this is updated if additional features are added.
      */
-    private static final long ALL_FEATURES = (1L << REF_CAPTURE + 1) - 1L; // 
MUST REMAIN PRIVATE
+    private static final long ALL_FEATURES = (1L << AMBIGUOUS_STATEMENT + 1) - 
1L; // MUST REMAIN PRIVATE
     /**
      * The default features flag mask.
      * <p>Meant for compatibility with scripts written before 3.3.1</p>
@@ -168,13 +168,12 @@ public final class JexlFeatures {
 
     /**
      * Protected future syntactic elements.
-     * <p><em>throw, switch, case, default, class, instanceof, jexl, 
$jexl</em></p>
+     * <p><em>class, jexl, $jexl</em></p>
      * @since 3.3.1
      */
     private static final Set<String> RESERVED_WORDS =
         Collections.unmodifiableSet(
-            new HashSet<>(Arrays.asList(
-                "switch", "case", "default", "class", "jexl", "$jexl")));
+            new HashSet<>(Arrays.asList( "class", "jexl", "$jexl")));
 
     /*
      * *WARNING*
@@ -719,6 +718,14 @@ public final class JexlFeatures {
         }
     }
 
+    public void setAmbiguousStatement(final boolean flag) {
+        setFeature(AMBIGUOUS_STATEMENT, flag);
+    }
+
+    public boolean supportsAmbiguousStatement() {
+        return getFeature(AMBIGUOUS_STATEMENT);
+    }
+
     /**
      * Sets whether side effect expressions are enabled.
      * <p>
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 1859e699..a0944e53 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -17,6 +17,7 @@
 package org.apache.commons.jexl3.internal;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
@@ -25,102 +26,7 @@ import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JexlScript;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-import org.apache.commons.jexl3.parser.ASTAnnotation;
-import org.apache.commons.jexl3.parser.ASTArguments;
-import org.apache.commons.jexl3.parser.ASTArrayAccess;
-import org.apache.commons.jexl3.parser.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
-import org.apache.commons.jexl3.parser.ASTBlock;
-import org.apache.commons.jexl3.parser.ASTBreak;
-import org.apache.commons.jexl3.parser.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-import org.apache.commons.jexl3.parser.ASTDivNode;
-import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
-import org.apache.commons.jexl3.parser.ASTEQNode;
-import org.apache.commons.jexl3.parser.ASTEQSNode;
-import org.apache.commons.jexl3.parser.ASTERNode;
-import org.apache.commons.jexl3.parser.ASTEWNode;
-import org.apache.commons.jexl3.parser.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-import org.apache.commons.jexl3.parser.ASTFunctionNode;
-import org.apache.commons.jexl3.parser.ASTGENode;
-import org.apache.commons.jexl3.parser.ASTGTNode;
-import org.apache.commons.jexl3.parser.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-import org.apache.commons.jexl3.parser.ASTIdentifier;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
-import org.apache.commons.jexl3.parser.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-import org.apache.commons.jexl3.parser.ASTInstanceOf;
-import org.apache.commons.jexl3.parser.ASTJexlLambda;
-import org.apache.commons.jexl3.parser.ASTJexlScript;
-import org.apache.commons.jexl3.parser.ASTJxltLiteral;
-import org.apache.commons.jexl3.parser.ASTLENode;
-import org.apache.commons.jexl3.parser.ASTLTNode;
-import org.apache.commons.jexl3.parser.ASTMapEntry;
-import org.apache.commons.jexl3.parser.ASTMapLiteral;
-import org.apache.commons.jexl3.parser.ASTMethodNode;
-import org.apache.commons.jexl3.parser.ASTModNode;
-import org.apache.commons.jexl3.parser.ASTMulNode;
-import org.apache.commons.jexl3.parser.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNESNode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-import org.apache.commons.jexl3.parser.ASTNRNode;
-import org.apache.commons.jexl3.parser.ASTNSWNode;
-import org.apache.commons.jexl3.parser.ASTNotInstanceOf;
-import org.apache.commons.jexl3.parser.ASTNotNode;
-import org.apache.commons.jexl3.parser.ASTNullLiteral;
-import org.apache.commons.jexl3.parser.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-import org.apache.commons.jexl3.parser.ASTReference;
-import org.apache.commons.jexl3.parser.ASTReferenceExpression;
-import org.apache.commons.jexl3.parser.ASTRegexLiteral;
-import org.apache.commons.jexl3.parser.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-import org.apache.commons.jexl3.parser.ASTSetXorNode;
-import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSizeFunction;
-import org.apache.commons.jexl3.parser.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTThrowStatement;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-import org.apache.commons.jexl3.parser.ASTTryResources;
-import org.apache.commons.jexl3.parser.ASTTryStatement;
-import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
-import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
-import org.apache.commons.jexl3.parser.ASTVar;
-import org.apache.commons.jexl3.parser.ASTWhileStatement;
-import org.apache.commons.jexl3.parser.JexlNode;
-import org.apache.commons.jexl3.parser.ParserVisitor;
-import org.apache.commons.jexl3.parser.StringParser;
+import org.apache.commons.jexl3.parser.*;
 
 /**
  * Helps pinpoint the cause of problems in expressions that fail during 
evaluation.
@@ -145,6 +51,9 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      * @return true if node is a statement
      */
     private static boolean isStatement(final JexlNode child) {
+        if (child instanceof ASTCaseStatement) {
+            return isStatement(child.jjtGetChild(0));
+        }
         return child instanceof ASTJexlScript
                 || child instanceof ASTBlock
                 || child instanceof ASTIfStatement
@@ -153,7 +62,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 || child instanceof ASTWhileStatement
                 || child instanceof ASTDoWhileStatement
                 || child instanceof ASTAnnotation
-                || child instanceof ASTThrowStatement;
+                || child instanceof ASTThrowStatement
+                || child instanceof ASTSwitchStatement;
     }
     /**
      * Tests whether a script or expression ends with a semicolumn.
@@ -189,7 +99,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                     builder.append("#pragma ");
                     builder.append(key);
                     builder.append(' ');
-                    builder.append(pragmaValue.toString());
+                    acceptValue(builder, pragmaValue, false);
                     builder.append('\n');
                 }
             }
@@ -260,7 +170,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     protected Object acceptStatement(final JexlNode child, final Object data) {
         final JexlNode parent = child.jjtGetParent();
-        if (indent > 0 && (parent instanceof ASTBlock || parent instanceof 
ASTJexlScript)) {
+        if (indent > 0 && (parent instanceof ASTBlock || parent instanceof 
ASTJexlScript || parent instanceof ASTSwitchStatement)) {
             for (int i = 0; i < indentLevel; ++i) {
                 for(int s = 0; s < indent; ++s) {
                     builder.append(' ');
@@ -554,6 +464,24 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         }
         return data;
     }
+    /**
+     * Accepts a (simple) value and appends its representation to the builder.
+     * @param builder where to append
+     * @param value   the value to append
+     */
+    static void acceptValue(StringBuilder builder, Object value, boolean 
quotedStrings) {
+        if (value == null) {
+            builder.append("null");
+        } else if (value instanceof String) {
+            builder.append(quotedStrings? 
StringParser.escapeString(value.toString(), '\'') : value.toString());
+        } else if (value instanceof Number) {
+            builder.append(new NumberParser((Number) value));
+        } else if (value instanceof Boolean) {
+            builder.append((Boolean) value ? "true" : "false");
+        } else {
+            builder.append(value.toString());
+        }
+    }
 
     /**
      * Resets this debugger state.
@@ -716,6 +644,10 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
 
     @Override
     protected Object visit(final ASTBlock node, final Object data) {
+        return acceptBlock(node, 0, data);
+    }
+
+    private Object acceptBlock(final JexlNode node, int begin, final Object 
data) {
         builder.append('{');
         if (indent > 0) {
             indentLevel += 1;
@@ -724,7 +656,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
             builder.append(' ');
         }
         final int num = node.jjtGetNumChildren();
-        for (int i = 0; i < num; ++i) {
+        for (int i = begin; i < num; ++i) {
             final JexlNode child = node.jjtGetChild(i);
             acceptStatement(child, data);
         }
@@ -776,6 +708,62 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return data;
     }
 
+    @Override
+    protected Object visit(ASTSwitchStatement node, Object data) {
+        builder.append("switch (");
+        accept(node.jjtGetChild(0), data);
+        builder.append(") ");
+        acceptBlock(node, 1, data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTCaseStatement node, Object data) {
+        List<Object> values = node.getValues();
+        if (values.isEmpty()) {
+            // default case
+            builder.append("default : ");
+        } else {
+            // regular case
+            for (Object value : values) {
+                builder.append("case ");
+                acceptValue(builder, value, true);
+                builder.append(" : ");
+            }
+        }
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
+    @Override
+    protected Object visit(ASTSwitchExpression node, Object data) {
+        return visit((ASTSwitchStatement) node, data);
+    }
+
+    @Override
+    protected Object visit(ASTCaseExpression node, Object data) {
+        List<Object> values = node.getValues();
+        if (values.isEmpty()) {
+            // default case
+            builder.append("default -> ");
+        } else {
+            builder.append("case -> ");
+            // regular case
+            boolean first = true;
+            for (Object value : values) {
+                if (!first) {
+                    builder.append(", ");
+                } else {
+                    first = false;
+                }
+                acceptValue(builder, value, true);
+            }
+            builder.append(" : ");
+        }
+        accept(node.jjtGetChild(0), data);
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTContinue node, final Object data) {
         return check(node, "continue", data);
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 350803f9..dc097f99 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -35,101 +35,7 @@ import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.JxltEngine;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-import org.apache.commons.jexl3.parser.ASTAnnotation;
-import org.apache.commons.jexl3.parser.ASTArguments;
-import org.apache.commons.jexl3.parser.ASTArrayAccess;
-import org.apache.commons.jexl3.parser.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
-import org.apache.commons.jexl3.parser.ASTBlock;
-import org.apache.commons.jexl3.parser.ASTBreak;
-import org.apache.commons.jexl3.parser.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-import org.apache.commons.jexl3.parser.ASTDivNode;
-import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
-import org.apache.commons.jexl3.parser.ASTEQNode;
-import org.apache.commons.jexl3.parser.ASTEQSNode;
-import org.apache.commons.jexl3.parser.ASTERNode;
-import org.apache.commons.jexl3.parser.ASTEWNode;
-import org.apache.commons.jexl3.parser.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-import org.apache.commons.jexl3.parser.ASTFunctionNode;
-import org.apache.commons.jexl3.parser.ASTGENode;
-import org.apache.commons.jexl3.parser.ASTGTNode;
-import org.apache.commons.jexl3.parser.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-import org.apache.commons.jexl3.parser.ASTIdentifier;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccessJxlt;
-import org.apache.commons.jexl3.parser.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-import org.apache.commons.jexl3.parser.ASTInstanceOf;
-import org.apache.commons.jexl3.parser.ASTJexlLambda;
-import org.apache.commons.jexl3.parser.ASTJexlScript;
-import org.apache.commons.jexl3.parser.ASTJxltLiteral;
-import org.apache.commons.jexl3.parser.ASTLENode;
-import org.apache.commons.jexl3.parser.ASTLTNode;
-import org.apache.commons.jexl3.parser.ASTMapEntry;
-import org.apache.commons.jexl3.parser.ASTMapLiteral;
-import org.apache.commons.jexl3.parser.ASTMethodNode;
-import org.apache.commons.jexl3.parser.ASTModNode;
-import org.apache.commons.jexl3.parser.ASTMulNode;
-import org.apache.commons.jexl3.parser.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNESNode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-import org.apache.commons.jexl3.parser.ASTNRNode;
-import org.apache.commons.jexl3.parser.ASTNSWNode;
-import org.apache.commons.jexl3.parser.ASTNotInstanceOf;
-import org.apache.commons.jexl3.parser.ASTNotNode;
-import org.apache.commons.jexl3.parser.ASTNullLiteral;
-import org.apache.commons.jexl3.parser.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-import org.apache.commons.jexl3.parser.ASTReference;
-import org.apache.commons.jexl3.parser.ASTReferenceExpression;
-import org.apache.commons.jexl3.parser.ASTRegexLiteral;
-import org.apache.commons.jexl3.parser.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-import org.apache.commons.jexl3.parser.ASTSetXorNode;
-import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSizeFunction;
-import org.apache.commons.jexl3.parser.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTThrowStatement;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-import org.apache.commons.jexl3.parser.ASTTryResources;
-import org.apache.commons.jexl3.parser.ASTTryStatement;
-import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
-import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
-import org.apache.commons.jexl3.parser.ASTVar;
-import org.apache.commons.jexl3.parser.ASTWhileStatement;
-import org.apache.commons.jexl3.parser.JexlNode;
+import org.apache.commons.jexl3.parser.*;
 
 /**
  * An interpreter of JEXL syntax.
@@ -1324,6 +1230,26 @@ 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;
+    }
+
+    @Override
+    protected Object visit(ASTCaseExpression node, Object data) {
+        return null;
+    }
+
     @Override
     protected Object visit(final ASTContinue node, final Object data) {
         throw new JexlException.Continue(node);
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 c1f1cf1b..0068ea13 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -18,100 +18,7 @@ package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlScript;
-import org.apache.commons.jexl3.parser.ASTAddNode;
-import org.apache.commons.jexl3.parser.ASTAndNode;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-import org.apache.commons.jexl3.parser.ASTAnnotation;
-import org.apache.commons.jexl3.parser.ASTArguments;
-import org.apache.commons.jexl3.parser.ASTArrayAccess;
-import org.apache.commons.jexl3.parser.ASTArrayLiteral;
-import org.apache.commons.jexl3.parser.ASTAssignment;
-import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
-import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
-import org.apache.commons.jexl3.parser.ASTBlock;
-import org.apache.commons.jexl3.parser.ASTBreak;
-import org.apache.commons.jexl3.parser.ASTConstructorNode;
-import org.apache.commons.jexl3.parser.ASTContinue;
-import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
-import org.apache.commons.jexl3.parser.ASTDefineVars;
-import org.apache.commons.jexl3.parser.ASTDivNode;
-import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
-import org.apache.commons.jexl3.parser.ASTEQNode;
-import org.apache.commons.jexl3.parser.ASTEQSNode;
-import org.apache.commons.jexl3.parser.ASTERNode;
-import org.apache.commons.jexl3.parser.ASTEWNode;
-import org.apache.commons.jexl3.parser.ASTEmptyFunction;
-import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
-import org.apache.commons.jexl3.parser.ASTFalseNode;
-import org.apache.commons.jexl3.parser.ASTForeachStatement;
-import org.apache.commons.jexl3.parser.ASTFunctionNode;
-import org.apache.commons.jexl3.parser.ASTGENode;
-import org.apache.commons.jexl3.parser.ASTGTNode;
-import org.apache.commons.jexl3.parser.ASTGetDecrementNode;
-import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
-import org.apache.commons.jexl3.parser.ASTIdentifier;
-import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
-import org.apache.commons.jexl3.parser.ASTIfStatement;
-import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
-import org.apache.commons.jexl3.parser.ASTInstanceOf;
-import org.apache.commons.jexl3.parser.ASTJexlScript;
-import org.apache.commons.jexl3.parser.ASTJxltLiteral;
-import org.apache.commons.jexl3.parser.ASTLENode;
-import org.apache.commons.jexl3.parser.ASTLTNode;
-import org.apache.commons.jexl3.parser.ASTMapEntry;
-import org.apache.commons.jexl3.parser.ASTMapLiteral;
-import org.apache.commons.jexl3.parser.ASTMethodNode;
-import org.apache.commons.jexl3.parser.ASTModNode;
-import org.apache.commons.jexl3.parser.ASTMulNode;
-import org.apache.commons.jexl3.parser.ASTNENode;
-import org.apache.commons.jexl3.parser.ASTNESNode;
-import org.apache.commons.jexl3.parser.ASTNEWNode;
-import org.apache.commons.jexl3.parser.ASTNRNode;
-import org.apache.commons.jexl3.parser.ASTNSWNode;
-import org.apache.commons.jexl3.parser.ASTNotInstanceOf;
-import org.apache.commons.jexl3.parser.ASTNotNode;
-import org.apache.commons.jexl3.parser.ASTNullLiteral;
-import org.apache.commons.jexl3.parser.ASTNullpNode;
-import org.apache.commons.jexl3.parser.ASTNumberLiteral;
-import org.apache.commons.jexl3.parser.ASTOrNode;
-import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
-import org.apache.commons.jexl3.parser.ASTRangeNode;
-import org.apache.commons.jexl3.parser.ASTReference;
-import org.apache.commons.jexl3.parser.ASTReferenceExpression;
-import org.apache.commons.jexl3.parser.ASTRegexLiteral;
-import org.apache.commons.jexl3.parser.ASTReturnStatement;
-import org.apache.commons.jexl3.parser.ASTSWNode;
-import org.apache.commons.jexl3.parser.ASTSetAddNode;
-import org.apache.commons.jexl3.parser.ASTSetAndNode;
-import org.apache.commons.jexl3.parser.ASTSetDivNode;
-import org.apache.commons.jexl3.parser.ASTSetLiteral;
-import org.apache.commons.jexl3.parser.ASTSetModNode;
-import org.apache.commons.jexl3.parser.ASTSetMultNode;
-import org.apache.commons.jexl3.parser.ASTSetOrNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSetSubNode;
-import org.apache.commons.jexl3.parser.ASTSetXorNode;
-import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightNode;
-import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
-import org.apache.commons.jexl3.parser.ASTSizeFunction;
-import org.apache.commons.jexl3.parser.ASTStringLiteral;
-import org.apache.commons.jexl3.parser.ASTSubNode;
-import org.apache.commons.jexl3.parser.ASTTernaryNode;
-import org.apache.commons.jexl3.parser.ASTThrowStatement;
-import org.apache.commons.jexl3.parser.ASTTrueNode;
-import org.apache.commons.jexl3.parser.ASTTryResources;
-import org.apache.commons.jexl3.parser.ASTTryStatement;
-import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
-import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
-import org.apache.commons.jexl3.parser.ASTVar;
-import org.apache.commons.jexl3.parser.ASTWhileStatement;
-import org.apache.commons.jexl3.parser.JexlNode;
-import org.apache.commons.jexl3.parser.ParserVisitor;
+import org.apache.commons.jexl3.parser.*;
 
 /**
  * Concrete visitor base, used for feature and operator controllers.
@@ -192,6 +99,26 @@ public class ScriptVisitor extends ParserVisitor {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTSwitchStatement node, final Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(final ASTCaseStatement node, final Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(final ASTSwitchExpression node, final Object data) {
+        return visitNode(node, data);
+    }
+
+    @Override
+    protected Object visit(final ASTCaseExpression node, final Object data) {
+        return visitNode(node, data);
+    }
+
     @Override
     protected Object visit(final ASTContinue node, final Object data) {
         return visitNode(node, data);
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java
new file mode 100644
index 00000000..fdbd2ec5
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+
+public class ASTCaseExpression extends ASTCaseStatement {
+
+  public ASTCaseExpression(int id) {
+    super(id);
+  }
+
+  @Override
+  public Object jjtAccept(ParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java
new file mode 100644
index 00000000..95e02eba
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ASTCaseStatement extends JexlNode {
+  private transient List<Object> values = Collections.emptyList();
+
+  public ASTCaseStatement(int id) {
+    super(id);
+  }
+
+  @Override
+  public Object jjtAccept(ParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+
+  public void setValue(Object value) {
+    if (value == null) {
+      this.values = Collections.emptyList();
+    } else {
+      this.values = Collections.singletonList(value);
+    }
+  }
+
+  public void setValues(List<Object> values) {
+    if (values == null) {
+      this.values = Collections.emptyList();
+    } else if (values.size() == 1) {
+      this.values = Collections.singletonList(values.get(0));
+    } else {
+      this.values = new ArrayList<>(values);
+    }
+  }
+
+  public List<Object> getValues() {
+    return Collections.unmodifiableList(values);
+  }
+}
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java
new file mode 100644
index 00000000..7e53a32d
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+
+public class ASTSwitchExpression extends ASTSwitchStatement {
+  public ASTSwitchExpression(int id) {
+    super(id);
+  }
+
+  public ASTSwitchExpression(Parser p, int id) {
+    super(p, id);
+  }
+
+  @Override
+  public Object jjtAccept(ParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
new file mode 100644
index 00000000..4690027e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ASTSwitchStatement extends JexlNode {
+  public ASTSwitchStatement(int id) {
+    super(id);
+  }
+
+  @Override
+  public Object jjtAccept(ParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+
+  /**
+   * The map of cases, where the key is the case value and the value is the 
switch index.
+   */
+  protected transient Map<Object, Integer> cases = Collections.emptyMap();
+
+  /**
+   * Returns the array of cases values.
+   * <p>Meant only for verification during tests.</p>
+   * The list at each index contains the case values for that index.
+   * If the values-list is empty for an index, it is the default case.
+   *
+   * @return an array of case values
+   */
+  public List<Object>[] getCasesList() {
+    @SuppressWarnings("unchecked")
+    List<Object>[] list = (List<Object>[]) new List[jjtGetNumChildren() -1];
+    for (Map.Entry<Object, Integer> entry : cases.entrySet()) {
+      int index = entry.getValue();
+      if (index < 0 || index >= list.length) {
+        throw new IndexOutOfBoundsException("switch index out of bounds: " + 
index);
+      }
+      List<Object> values = list[index];
+      if (values == null) {
+        list[index] = values = new ArrayList<>();
+      }
+      values.add(entry.getValue());
+    }
+    return list;
+  }
+
+  @SuppressWarnings("unchecked")
+  public void setCases(Map cases) {
+    this.cases = cases == null ? Collections.emptyMap() : (Map<Object, 
Integer>) cases;
+  }
+
+  Map<Object, Integer> getCases() {
+    return cases;
+  }
+
+  public int switchIndex(Object value) {
+    Object code = JexlParser.switchCode(value);
+    Integer index = cases.get(code);
+    if (index == null) {
+      index = cases.get(JexlParser.DFLT);
+    }
+    if (index != null && index >= 0 && index < jjtGetNumChildren()) {
+      return index; // index is 1-based, children are 0-based
+    }
+    return -1;
+  }
+
+  public static class Helper {
+    private int nswitch = 0;
+    private boolean defaultDefined = false;
+    private final Map<Object, Integer> dispatch = new LinkedHashMap<>();
+
+    void defineCase(JexlParser.SwitchSet constants) throws ParseException {
+      if (constants.isEmpty()) {
+        if (defaultDefined) {
+          throw new ParseException("default clause is already defined");
+        } else {
+          defaultDefined = true;
+          dispatch.put(JexlParser.DFLT, nswitch);
+        }
+      } else {
+        for (Object constant : constants) {
+          if (dispatch.put(constant == null ? JexlParser.NIL : constant, 
nswitch) != null) {
+            throw new ParseException("duplicate case in switch statement for 
value: " + constant);
+          }
+        }
+        constants.clear();
+      }
+      nswitch += 1;
+    }
+
+    void defineSwitch(ASTSwitchStatement statement) {
+      statement.cases = dispatch;
+    }
+  }
+
+}
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 3ed78de9..b971a096 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -21,9 +21,11 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayDeque;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
@@ -221,9 +223,125 @@ public abstract class JexlParser extends StringParser 
implements JexlScriptParse
      */
     protected final Map<LexicalUnit, Scope> blockScopes = new 
IdentityHashMap<>();
 
+    /**
+     * The name of the null case constant.
+     */
+    public static final Object NIL = new Object() {
+        @Override public String toString() {
+        return "null";
+    }};
+    /**
+     * The name of the default case constant.
+     */
+    public static final Object DFLT = new Object() {
+    @Override public String toString() {
+        return "default";
+    }};
+    /**
+     * The name of the default NaN constant.
+     */
+    public static final Object NAN = new Object() {
+    @Override public String toString() {
+        return "NaN";
+    }};
+
+    /**
+     * Encode a value to a switch predicate.
+     * @param value the value
+     * @return the encoded value, which is either the value itself, or NAN 
(for NaN) or NIL (for null)
+     */
+    static Object switchCode(Object value) {
+        if (value == null) {
+            return NIL;
+        }
+        if (value instanceof Double && ((Double) value).isNaN()) {
+            return NAN;
+        }
+        return value;
+    }
+
+    /**
+     * Decodes a value of a switch predicate.
+     * @param value an encoded value, which is either a value or NAN (for NaN) 
or NIL (for null)
+     * @return the decoded value
+     */
+    static Object switchDecode(Object value) {
+        if (value == NIL) {
+            return null;
+        }
+        if (value == NAN) {
+            return Double.NaN;
+        }
+        return value;
+    }
+
+    /**
+     * Constructs a set of constants amenable to switch expression.
+     */
+    protected SwitchSet switchSet() {
+        return new SwitchSet();
+    }
+    protected class SwitchSet implements Iterable<Object> {
+        private final Set<Object> values = new LinkedHashSet<>();
+        /**
+         * Adds a collection of values to the set.
+         * @param values the values to add
+         */
+        void addAll(Collection<Object> values) {
+            for (Object value : values) {
+                add(value);
+            }
+        }
+
+        /**
+         * Adds a value to the set.
+         * @param value the value to add
+         */
+        void add(Object value) {
+            Object code = switchCode(value);
+            if (!values.add(code)) {
+                throw new JexlException.Parsing(info, "duplicate constant 
value: " + value);
+            }
+        }
+
+        void clear() {
+            values.clear();
+        }
+
+        boolean isEmpty() {
+            return values.isEmpty();
+        }
+
+        int size() {
+            return values.size();
+        }
+
+        @Override
+        public Iterator<Object> iterator() {
+            return new Iterator<Object>() {
+                private final Iterator<Object> iter = values.iterator();
+
+                @Override
+                public boolean hasNext() {
+                    return iter.hasNext();
+                }
+
+                @Override
+                public Object next() {
+                    return switchDecode(iter.next());
+                }
+
+                @Override
+                public void remove() {
+                    iter.remove();
+                }
+            };
+        }
+    }
+
     /**
      * Internal, for debug purpose only.
-     * @param registers whether register syntax is recognized by this parser
+     * @param registers sets whether this parser recognizes the register syntax
      */
     public void allowRegisters(final boolean registers) {
         featureController.setFeatures(new 
JexlFeatures(featureController.getFeatures()).register(registers));
diff --git a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java 
b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
index aa5a9aa2..4ebc147b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
@@ -43,6 +43,20 @@ public final class NumberParser implements Serializable {
         return new NumberParser().assignNatural(isNegative(negative), 
s.image).getLiteralValue();
     }
 
+    public NumberParser() {
+        this(null);
+    }
+
+    public NumberParser(Number number) {
+        if (number != null) {
+            this.literal = number;
+            this.clazz = number.getClass();
+        } else {
+            this.literal = null;
+            this.clazz = null;
+        }
+    }
+
     /** The type literal value. */
     private Number literal;
 
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 0b32a451..eaaef134 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -39,6 +39,7 @@ package org.apache.commons.jexl3.parser;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.ArrayList;
 import java.util.LinkedList;
 
 import org.apache.commons.jexl3.JexlInfo;
@@ -71,7 +72,11 @@ public final class Parser extends JexlParser
             throw new JexlException.Tokenization(info, xtme).clean();
         } catch (ParseException xparse) {
             Token errortok = errorToken(jj_lastpos, jj_scanpos, token.next, 
token);
-            throw new JexlException.Parsing(info.at(errortok.beginLine, 
errortok.beginColumn), errortok.image).clean();
+            String msg = xparse.getMessage();
+            if (msg.endsWith(";")) {
+              msg = errortok.image;
+            }
+            throw new JexlException.Parsing(info.at(errortok.beginLine, 
errortok.beginColumn), msg).clean();
         } finally {
             token_source.defaultLexState = DEFAULT;
             cleanup(previous);
@@ -238,6 +243,9 @@ TOKEN_MGR_DECLS : {
     | < NULL : "null" >
     | < TRUE : "true" >
     | < FALSE : "false" >
+    | < SWITCH : "switch" >
+    | < CASE : "case" >
+    | < CASE_DEFAULT : "default" >
 }
 
 
@@ -413,10 +421,11 @@ void StatementNoVar() #void : {}
     | LOOKAHEAD(<BREAK>) Break()
     | LOOKAHEAD(<THROW>) ThrowStatement()
     | LOOKAHEAD(<TRY>) TryStatement()
+    | LOOKAHEAD(<SWITCH>) SwitchStatement()
     | LOOKAHEAD(LambdaLookahead()) Lambda()
     | LOOKAHEAD(Expression()) ExpressionStatement()
     | Block()
-    | LOOKAHEAD(<VAR>, {!getFeatures().isLexical()} ) Var()
+    | LOOKAHEAD(<VAR>, { !getFeatures().isLexical()} ) Var()
 }
 
 void Block() #Block : {}
@@ -431,7 +440,7 @@ void FunctionStatement() #JexlLambda : {}
 
 void ExpressionStatement() #void : {}
 {
-    Expression() (LOOKAHEAD(Expression()) Expression() #Ambiguous(1))* 
(LOOKAHEAD(1) <SEMICOL>)?
+    Expression() (LOOKAHEAD(Expression()/*, { 
getFeatures().supportsAmbiguousStatement() }*/) Expression() #Ambiguous(1))* 
(LOOKAHEAD(1) <SEMICOL>)?
 }
 
 
@@ -579,6 +588,99 @@ void DeclareFunction() #Var :
     t=<IDENTIFIER> { declareFunction(jjtThis, t); }
 }
 
+
+void SwitchStatement() #SwitchStatement :
+{
+    ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper();
+    SwitchSet cases = switchSet();
+}
+{
+    <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY>
+      ( SwitchStatementCase(cases) { helper.defineCase(cases); } )*
+    <RCURLY>
+{
+    helper.defineSwitch(jjtThis);
+}
+}
+
+
+void SwitchStatementCase(SwitchSet cases) #CaseStatement :
+{
+    Object constant;
+    List<Object> constants;
+    loopCount += 1;
+}
+{
+    ( LOOKAHEAD(3)
+       ( <CASE> constant=constLiteral() <COLON> )+
+        { constants = Collections.singletonList(constant); 
jjtThis.setValues(constants); cases.addAll(constants); }
+        |
+        <CASE_DEFAULT> <COLON>
+    )
+    ( LOOKAHEAD(1) Block() | StatementNoVar() )
+{
+    loopCount -= 1;
+}
+}
+
+void SwitchExpression() #SwitchExpression :
+{
+    ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper();
+    SwitchSet cases = switchSet();
+}
+{
+    <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY>
+      ( SwitchExpressionCase(cases) { helper.defineCase(cases); } )*
+    <RCURLY>
+{
+    helper.defineSwitch(jjtThis);
+}
+}
+
+void SwitchExpressionCase(SwitchSet cases) #CaseExpression :
+{
+    List<Object> constants = new ArrayList(1);
+}
+{
+    ( LOOKAHEAD(2)
+        <CASE> ConstLiterals(constants) <LAMBDA> { cases.addAll(constants); 
jjtThis.setValues(constants); }
+        |
+        <CASE_DEFAULT> <LAMBDA>
+    )
+    ( LOOKAHEAD(3) Block() | Expression())
+}
+
+void ConstLiterals(List<Object> constants) #void :
+{
+    Object constant;
+}
+{
+    constant=constLiteral() { constants.add(constant); } (LOOKAHEAD(2) <COMMA> 
constant=constLiteral() { constants.add(constant); })*
+}
+
+
+Object constLiteral() #void :
+{
+Token s = null;
+Token v;
+Object result;
+}
+{
+  (
+      LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<INTEGER_LITERAL> { result = 
NumberParser.parseInteger(s, v); }
+    | LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<FLOAT_LITERAL> { result = 
NumberParser.parseDouble(s, v); }
+    | LOOKAHEAD(1) v=<STRING_LITERAL> { result = Parser.buildString(v.image, 
true); }
+    | LOOKAHEAD(1) <TRUE> { result = true; }
+    | LOOKAHEAD(1) <FALSE> { result = false; }
+    | LOOKAHEAD(1) <NULL> { result = null; }
+    | LOOKAHEAD(1) <NAN_LITERAL> { result = Double.NaN; }
+  )
+  {
+    return result;
+  }
+}
+
+
 void Pragma() #void :
 {
     LinkedList<String> lstr = new LinkedList<String>();
@@ -1054,7 +1156,6 @@ void LambdaLookahead() #void : {}
 void Lambda() #JexlLambda :
 {
    Token arrow;
-   Token name;
 }
 {
   <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction())? {
@@ -1132,6 +1233,8 @@ void PrimaryExpression() #void : {}
        LOOKAHEAD( <LBRACKET> ) ArrayLiteral()
     |
        LOOKAHEAD( <NEW> ) Constructor()
+    |
+       LOOKAHEAD( <SWITCH> ) SwitchExpression()
     |
        LOOKAHEAD( FunctionCallLookahead() ) FunctionCall()
     |
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 2d1ffbe1..7ba32e41 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -60,6 +60,14 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTConstructorNode node, Object data);
 
+    protected abstract Object visit(ASTSwitchStatement node, final Object 
data);
+
+    protected abstract Object visit(ASTCaseStatement node, final Object data);
+
+    protected abstract Object visit(ASTSwitchExpression node, final Object 
data);
+
+    protected abstract Object visit(ASTCaseExpression node, final Object data);
+
     protected abstract Object visit(ASTContinue node, Object data);
 
     protected abstract Object visit(ASTDecrementGetNode node, final Object 
data);
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java 
b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index f7ed0d1c..161a3660 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.commons.jexl3;
 
-import static org.apache.commons.jexl3.JexlFeatures.REF_CAPTURE;
+import static org.apache.commons.jexl3.JexlFeatures.AMBIGUOUS_STATEMENT;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -71,8 +71,8 @@ public class FeaturesTest extends JexlTestCase {
     @Test
     void test410a() {
         final long x = JexlFeatures.createAll().getFlags();
-        assertEquals(REF_CAPTURE + 1, Long.bitCount(x));
-        assertTrue((x & 1L << REF_CAPTURE) != 0);
+        assertEquals(AMBIGUOUS_STATEMENT + 1, Long.bitCount(x));
+        assertTrue((x & 1L << AMBIGUOUS_STATEMENT) != 0);
 
         final JexlFeatures all = JexlFeatures.createAll();
         final JexlEngine jexl = new JexlBuilder().features(all).create();
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 0a62b5bb..93a530bb 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -271,7 +271,7 @@ public class Issues400Test {
         Number result = (Number) script.execute(null, 99.0d, 7.82d);
         assertEquals(0d, result.doubleValue(), 8.e-15);
         // using BigdDecimal, more precise, still not zero
-        result = (Number) script.execute(null, new BigDecimal(99.0d), new 
BigDecimal(7.82d));
+        result = (Number) script.execute(null, new BigDecimal("99.0"), new 
BigDecimal("7.82"));
         assertEquals(0d, result.doubleValue(), 3.e-32);
     }
 
@@ -561,7 +561,6 @@ public class Issues400Test {
         assertNotNull(script);
         final Object result = script.execute(null);
         assertNull(result);
-
     }
 
     public static class Arithmetic435 extends JexlArithmetic {
@@ -727,4 +726,27 @@ public class Issues400Test {
         Assertions.assertTrue((boolean) sqle.createScript("a = 25", 
"a").execute(null, 25));
         Assertions.assertFalse((boolean) sqle.createScript("a != 25", 
"a").execute(null, 25));
     }
+
+    @Test
+    void test440a() {
+        JexlFeatures f = JexlFeatures.createDefault();
+        f.setAmbiguousStatement(true);
+        JexlEngine jexl = new 
JexlBuilder().features(f).safe(false).strict(true).create();
+        final String src =
+"let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\ndefault -> { let z = 
4; z + 6 } } y";
+        final JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+    }
+    @Test
+    void test440b() {
+        JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
+        final String src =
+"switch (x) { case 10 : 3; case 20 : case 21 : 4; default : return 5; } ";
+        final JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java 
b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index 3938add5..bf8df85b 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -32,6 +32,8 @@ import org.apache.commons.jexl3.internal.Closure;
 import org.apache.commons.jexl3.internal.Script;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 
 /**
  * Tests function/lambda/closure features.
@@ -213,18 +215,16 @@ class LambdaTest extends JexlTestCase {
         assertEquals(42, result);
     }
 
-    @Test void testFailParseFunc0() {
-        final String src = "if (false) function foo(x) { x + x }; var foo = 1";
+    @ParameterizedTest
+    @CsvSource({
+            "'if (false) function foo(x) { x + x }; var foo = 1', 'function'",
+            "'if (false) let foo = (x) -> { x + x }; var foo = 1', 'let'",
+            "'function foo(x) { x + x }; var foo = 42', 'foo'"
+    })
+    void testFailParseFunc(String src, String expectedKeyword) {
         final JexlEngine jexl = createEngine();
         final JexlException.Parsing xparse = 
assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src));
-        assertTrue(xparse.getMessage().contains("function"));
-    }
-
-    @Test void testFailParseFunc1() {
-        final String src = "if (false) let foo = (x) { x + x }; var foo = 1";
-        final JexlEngine jexl = createEngine();
-        final JexlException.Parsing xparse = 
assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src));
-        assertTrue(xparse.getMessage().contains("let"));
+        assertTrue(xparse.getMessage().contains(expectedKeyword));
     }
 
     @Test void testFatFact0() {
@@ -408,12 +408,6 @@ class LambdaTest extends JexlTestCase {
         assertEquals(simpleWhitespace(src), parsed);
     }
 
-    @Test void testNamedFuncIsConst() {
-        final String src = "function foo(x) { x + x }; var foo ='nonononon'";
-        final JexlEngine jexl = createEngine();
-        final JexlException.Parsing xparse = 
assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src));
-        assertTrue(xparse.getMessage().contains("foo"));
-    }
 
     @Test void testNestLambada() throws Exception {
         final JexlEngine jexl = createEngine();

Reply via email to