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

(commons-jexl) branch JEXL-440 created (now c224a05a)

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

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


      at c224a05a JEXL-440 : added switch/case/default syntax for statement and expressions; - Debugger handling of new AST nodes;

This branch includes the following new commits:

     new c224a05a JEXL-440 : added switch/case/default syntax for statement and expressions; - Debugger handling of new AST nodes;

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



(commons-jexl) 01/01: JEXL-440 : added switch/case/default syntax for statement and expressions; - Debugger handling of new AST nodes;

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