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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2ad56eaf JEXL-440 : clean up grammar; - various nits for next release;
2ad56eaf is described below

commit 2ad56eaf931f3c8c8a14a365d2088b8ea4f61ed3
Author: Henrib <[email protected]>
AuthorDate: Wed Nov 5 14:03:54 2025 -0800

    JEXL-440 : clean up grammar;
    - various nits for next release;
---
 pom.xml                                            |  2 -
 .../org/apache/commons/jexl3/JexlFeatures.java     |  5 ++
 .../apache/commons/jexl3/internal/Debugger.java    | 38 +++++++---
 .../apache/commons/jexl3/internal/Interpreter.java |  9 +++
 .../commons/jexl3/introspection/JexlUberspect.java |  3 +-
 .../commons/jexl3/parser/ASTCaseStatement.java     | 12 +--
 .../commons/jexl3/parser/ASTSwitchExpression.java  |  1 +
 .../commons/jexl3/parser/ASTSwitchStatement.java   | 40 +++++-----
 .../apache/commons/jexl3/parser/JexlParser.java    | 14 +++-
 .../org/apache/commons/jexl3/parser/Parser.jjt     | 80 ++++++++++----------
 .../org/apache/commons/jexl3/Issues400Test.java    | 74 ++++++++----------
 .../java/org/apache/commons/jexl3/SwitchTest.java  | 87 +++++++++++++++++++++-
 12 files changed, 232 insertions(+), 133 deletions(-)

diff --git a/pom.xml b/pom.xml
index 33c6f350..645f1ffa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -296,8 +296,6 @@
                         <onlyModified>true</onlyModified>
                         <accessModifier>protected</accessModifier>
                         <ignoreMissingClasses>true</ignoreMissingClasses>
-                        
<breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
-                        
<breakBuildIfCausedByExclusion>false</breakBuildIfCausedByExclusion>
                     </parameter>
                 </configuration>
             </plugin>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 9fb0516f..19b78f05 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -127,6 +127,11 @@ public final class JexlFeatures {
     public static final int REF_CAPTURE = 24;
     /** Ambiguous or strict statement allowed. */
     public static final int AMBIGUOUS_STATEMENT = 25;
+    /** Bad naming, use AMBIGUOUS_STATEMENT.
+     * @deprecated 3.6
+     */
+    @Deprecated
+    public static final int STRICT_STATEMENT = 25;
     /**
      * All features.
      * Ensure this is updated if additional features are added.
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 adb7683a..c592e5d5 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -52,7 +52,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      */
     private static boolean isStatement(final JexlNode child) {
         if (child instanceof ASTCaseStatement) {
-            return isStatement(child.jjtGetChild(0));
+            return child.jjtGetNumChildren() > 0 && 
isStatement(child.jjtGetChild(0));
         }
         return child instanceof ASTJexlScript
                 || child instanceof ASTBlock
@@ -718,8 +718,23 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
     }
 
     @Override
-    protected Object visit(final ASTCaseStatement node, final Object data) {
-        final List<Object> values = node.getValues();
+    protected Object visit(final ASTSwitchExpression node, Object data) {
+        return visit((ASTSwitchStatement) node, data);
+    }
+
+    @Override
+    protected Object visit(final ASTCaseStatement node, Object data) {
+        JexlNode parent = node.jjtGetParent();
+        boolean isStatement = parent instanceof ASTSwitchStatement && 
((ASTSwitchStatement) parent).isStatement();
+        if (isStatement) {
+            return visitCaseStatement(node, data);
+        } else {
+            return visitCaseExpression(node, data);
+        }
+    }
+
+    private Object visitCaseStatement(ASTCaseStatement node, Object data) {
+        List<Object> values = node.getValues();
         if (values.isEmpty()) {
             // default case
             builder.append("default : ");
@@ -731,23 +746,24 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 builder.append(" : ");
             }
         }
-        accept(node.jjtGetChild(0), data);
+        if (node.jjtGetNumChildren() > 0) {
+            accept(node.jjtGetChild(0), data);
+        }
         return data;
     }
 
     @Override
-    protected Object visit(final ASTSwitchExpression node, final Object data) {
-        return visit((ASTSwitchStatement) node, data);
+    protected Object visit(ASTCaseExpression node, Object data) {
+        return visitCaseExpression(node, data);
     }
 
-    @Override
-    protected Object visit(final ASTCaseExpression node, final Object data) {
-        final List<Object> values = node.getValues();
+    private Object visitCaseExpression(final ASTCaseStatement node, Object 
data) {
+        List<Object> values = node.getValues();
         if (values.isEmpty()) {
             // default case
             builder.append("default -> ");
         } else {
-            builder.append("case -> ");
+            builder.append("case ");
             // regular case
             boolean first = true;
             for (final Object value : values) {
@@ -758,7 +774,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 }
                 acceptValue(builder, value, true);
             }
-            builder.append(" : ");
+            builder.append(" -> ");
         }
         accept(node.jjtGetChild(0), data);
         return 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 a86411b0..fbe46cc7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -1865,6 +1865,12 @@ public class Interpreter extends InterpreterBase {
             ? node.jjtGetChild(0).jjtAccept(this, data)
             : null;
         cancelCheck(node);
+        JexlNode parent = node.jjtGetParent();
+        // if return is last child of script, no need to throw
+        if (parent instanceof ASTJexlScript &&
+            parent.jjtGetChild(parent.jjtGetNumChildren() - 1) == node) {
+            return val;
+        }
         throw new JexlException.Return(node, null, val);
     }
 
@@ -2031,6 +2037,9 @@ public class Interpreter extends InterpreterBase {
         Object value = node.jjtGetChild(0).jjtAccept(this, data);
         final int index = node.switchIndex(value);
         if (index > 0) {
+            if (!node.isStatement()) {
+                return node.jjtGetChild(index).jjtAccept(this, data);
+            }
             for (int i = index; i < count; ++i) {
                 try {
                     // evaluate the switch body
diff --git 
a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java 
b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
index 6ed6ce8d..6895db73 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
@@ -80,7 +80,7 @@ public interface JexlUberspect {
     /**
      * A marker interface that solves a simple class name into a fully 
qualified one.
      * <p>The base implementation uses imports.</p>
-     * @since 3.3
+     * @since 3.6
      */
     interface ClassNameResolver {
         /**
@@ -107,6 +107,7 @@ public interface JexlUberspect {
 
     /**
      * The factory type for creating constant resolvers.
+     * @since 3.6
      */
     interface ConstantResolverFactory {
         /**
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java
index 1a87f007..8f8d360d 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java
@@ -22,8 +22,6 @@ import java.util.Collections;
 import java.util.List;
 
 public class ASTCaseStatement extends JexlNode {
-  /** Pointless serial UID */
-  private static final long serialVersionUID = 1L;
 
   /** The values of the case statement. */
   protected List<Object> values = Collections.emptyList();
@@ -37,15 +35,7 @@ public class ASTCaseStatement extends JexlNode {
     return visitor.visit(this, data);
   }
 
-  public void setValue(final Object value) {
-    if (value == null) {
-      this.values = Collections.emptyList();
-    } else {
-      this.values = Collections.singletonList(value);
-    }
-  }
-
-  public void setValues(final List<Object> values) {
+  void setValues(final List<Object> values) {
     if (values == null) {
       this.values = Collections.emptyList();
     } else if (values.size() == 1) {
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java
index c7413b89..288ca677 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java
@@ -20,6 +20,7 @@ package org.apache.commons.jexl3.parser;
 public class ASTSwitchExpression extends ASTSwitchStatement {
   public ASTSwitchExpression(final int id) {
     super(id);
+    isStatement = false;
   }
 
   @Override
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
index a42b55f3..03d32399 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
@@ -23,9 +23,10 @@ import java.util.List;
 import java.util.Map;
 
 public class ASTSwitchStatement extends JexlNode {
-  /** Pointless serial UID */
-  private static final long serialVersionUID = 1L;
-
+  /**
+   * Whether this switch is a statement (true) or an expression (false).
+   */
+  protected boolean isStatement = true;
   /**
    * The map of cases, where the key is the case value and the value is the 
switch index.
    */
@@ -50,8 +51,7 @@ public class ASTSwitchStatement extends JexlNode {
    */
   public List<Object>[] getCasesList() {
     @SuppressWarnings("unchecked")
-    final
-    List<Object>[] list = (List<Object>[]) new List[jjtGetNumChildren() -1];
+    final List<Object>[] list = new List[jjtGetNumChildren() -1];
     for (final Map.Entry<Object, Integer> entry : cases.entrySet()) {
       final int index = entry.getValue();
       if (index < 0 || index >= list.length) {
@@ -66,13 +66,8 @@ public class ASTSwitchStatement extends JexlNode {
     return list;
   }
 
-  @SuppressWarnings("unchecked")
-  public void setCases(final Map cases) {
-    this.cases = cases == null ? Collections.emptyMap() : (Map<Object, 
Integer>) cases;
-  }
-
-  Map<Object, Integer> getCases() {
-    return cases;
+  public boolean isStatement() {
+    return isStatement;
   }
 
   public int switchIndex(final Object value) {
@@ -92,29 +87,30 @@ public class ASTSwitchStatement extends JexlNode {
    * <p>It detects duplicates cases and default.</p>
    */
   public static class Helper {
-    private int nswitch = 1; // switch index, starts at 1 since the first 
child is the switch expression
+    private int switchIndex = 1; // switch index, starts at 1 since the first 
child is the switch expression
     private boolean defaultDefined = false;
     private final Map<Object, Integer> dispatch = new LinkedHashMap<>();
 
-    void defineCase(final JexlParser.SwitchSet constants) throws 
ParseException {
-      if (constants.isEmpty()) {
+    void defineCase(final JexlParser.SwitchSet switchSet) throws 
ParseException {
+      if (switchSet.isEmpty()) {
         if (defaultDefined) {
           throw new ParseException("default clause is already defined");
+        } else {
+          defaultDefined = true;
+          dispatch.put(JexlParser.DFLT, switchIndex);
         }
-        defaultDefined = true;
-          dispatch.put(JexlParser.DFLT, nswitch);
       } else {
-        for (final Object constant : constants) {
-          if (dispatch.put(constant == null ? JexlParser.NIL : constant, 
nswitch) != null) {
+        for (final Object constant : switchSet) {
+          if (dispatch.put(constant == null ? JexlParser.NIL : constant, 
switchIndex) != null) {
             throw new ParseException("duplicate case in switch statement for 
value: " + constant);
           }
         }
-        constants.clear();
+        switchSet.clear();
       }
-      nswitch += 1;
+      switchIndex += 1;
     }
 
-    void defineSwitch(final ASTSwitchStatement statement) {
+    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 f3674d07..9f2c85e1 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -367,16 +367,14 @@ public abstract class JexlParser extends StringParser 
implements JexlScriptParse
     }
 
     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.
+         * @param values the values to add
          */
         void addAll(final Collection<Object> values) {
-            for (final Object value : values) {
+            for (Object value : values) {
                 add(value);
             }
         }
@@ -1079,10 +1077,18 @@ public abstract class JexlParser extends StringParser 
implements JexlScriptParse
         blockReference.set(unit);
     }
 
+    /**
+     * Escape any outer (parent) loops.
+     * <p>A lambda definition embedded in a for-block escapes that block;
+     * break/continue are not valid within that lambda.</p>
+     */
     protected void pushLoop() {
         loopCounts.push(loopCount.getAndSet(0));
     }
 
+    /**
+     * Restores the previous loop count.
+     */
     protected void popLoop() {
         if (!loopCounts.isEmpty()) {
             loopCount.set(loopCounts.pop());
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 282e449c..59eee701 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -362,7 +362,7 @@ TOKEN_MGR_DECLS : {
   > : DEFAULT
 }
 
-<DEFAULT> TOKEN :
+<*> TOKEN :
 {
   < REGEX_LITERAL:
     "~" "/" (~["/","\n","\r","\t","\f","\b","\u2028","\u2029"] | "\\" "/" )* 
"/"
@@ -381,7 +381,7 @@ ASTJexlScript JexlScript(Scope frame, JexlFeatures 
features) : {
    {
         pushUnit(jjtThis);
    }
-        (LOOKAHEAD(<IMPORT>) Import())* ( LOOKAHEAD(<PRAGMA>) Pragma() | { 
controlPragmaAnywhere(); } Statement() )* <EOF>
+        ( Import())* ( Pragma() | { controlPragmaAnywhere(); } Statement() )* 
<EOF>
    {
         popUnit(jjtThis);
         return jjtThis.script();
@@ -408,37 +408,37 @@ void Annotation() #Annotation :
     Token t;
 }
 {
-    t=<ANNOTATION> { jjtThis.setName(t.image); } (LOOKAHEAD(<LPAREN>) 
Arguments() )?
+    t=<ANNOTATION> { jjtThis.setName(t.image); } (LOOKAHEAD(2) Arguments() )?
 }
 
 void AnnotatedStatement() #AnnotatedStatement : {}
  {
-    (LOOKAHEAD(<ANNOTATION>) Annotation())+ (LOOKAHEAD(1) Block() | 
Statement())
+    (LOOKAHEAD(2) Annotation())+ Statement()
  }
 
 void Statement() #void : {}
 {
-    LOOKAHEAD(<LET>|<CONST>|<VAR>) Var()
+    LOOKAHEAD(3) Var()
     |
-    LOOKAHEAD(<FUNCTION> <IDENTIFIER>) FunctionStatement()
+    LOOKAHEAD(3) FunctionStatement()
     |
-    StatementNoVar()
+    StatementSink()
 }
 
-void StatementNoVar() #void : {}
+void StatementSink() #void : {}
 {
     <SEMICOL>
-    | LOOKAHEAD(<ANNOTATION>) AnnotatedStatement()
-    | LOOKAHEAD(<IF>) IfStatement()
-    | LOOKAHEAD(<FOR>) ForeachStatement()
-    | LOOKAHEAD(<WHILE>) WhileStatement()
-    | LOOKAHEAD(<DO>) DoWhileStatement()
-    | LOOKAHEAD(<RETURN>) ReturnStatement()
-    | LOOKAHEAD(<CONTINUE>) Continue()
-    | LOOKAHEAD(<BREAK>) Break()
-    | LOOKAHEAD(<THROW>) ThrowStatement()
-    | LOOKAHEAD(<TRY>) TryStatement()
-    | LOOKAHEAD(<SWITCH>) SwitchStatement()
+    | AnnotatedStatement()
+    | IfStatement()
+    | ForeachStatement()
+    | WhileStatement()
+    | DoWhileStatement()
+    | ReturnStatement()
+    | Continue()
+    | Break()
+    | ThrowStatement()
+    | TryStatement()
+    | LOOKAHEAD(3) SwitchStatement()
     | LOOKAHEAD(LambdaLookahead()) Lambda()
     | LOOKAHEAD(Expression()) ExpressionStatement()
     | Block()
@@ -447,7 +447,7 @@ void StatementNoVar() #void : {}
 
 void Block() #Block : {}
 {
-    <LCURLY> { pushUnit(jjtThis); } ( LOOKAHEAD(<PRAGMA>)  Pragma() | 
Statement() )* { popUnit(jjtThis); } <RCURLY>
+    <LCURLY> { pushUnit(jjtThis); } ( Pragma() | Statement() )* { 
popUnit(jjtThis); } <RCURLY>
 }
 
 void FunctionStatement() #JexlLambda : {}
@@ -457,22 +457,22 @@ void FunctionStatement() #JexlLambda : {}
 
 void ExpressionStatement() #void : {}
 {
-    Expression() (LOOKAHEAD(Expression(), { isAmbiguousStatement(SEMICOL) } ) 
Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)*
+    Expression() (LOOKAHEAD(2, { isAmbiguousStatement(SEMICOL) } ) 
Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)?
 }
 
 
 void IfStatement() : {}
 {
-    <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) Block() | 
StatementNoVar())
-    ( LOOKAHEAD(2) <ELSE> <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) 
Block() | StatementNoVar()) )*
-    ( LOOKAHEAD(1) <ELSE>  (LOOKAHEAD(1) Block() | StatementNoVar()) )?
+    <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) Block() | 
StatementSink())
+    ( LOOKAHEAD(2) <ELSE> <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) 
Block() | StatementSink()) )*
+    ( LOOKAHEAD(1) <ELSE>  (LOOKAHEAD(1) Block() | StatementSink()) )?
 }
 
 void TryStatement() : {}
 {
-     <TRY> (LOOKAHEAD(1) TryResources() | Block())
-     (LOOKAHEAD(1) <CATCH> { pushUnit(jjtThis); } <LPAREN> InlineVar() 
<RPAREN> Block() { jjtThis.catchClause(); popUnit(jjtThis);})?
-     (LOOKAHEAD(1) <FINALLY>  Block() { jjtThis.finallyClause(); })?
+     <TRY> ( TryResources() | Block())
+     ( <CATCH> { pushUnit(jjtThis); } <LPAREN> InlineVar() <RPAREN> Block() { 
jjtThis.catchClause(); popUnit(jjtThis);})?
+     ( <FINALLY>  Block() { jjtThis.finallyClause(); } )?
 }
 
 void TryResources() : {}
@@ -496,12 +496,12 @@ LOOKAHEAD(2) Var() | Identifier(true)
 
 void WhileStatement() : {}
 {
-    <WHILE> <LPAREN> Expression() <RPAREN>  { loopCount.incrementAndGet(); }  
(LOOKAHEAD(1) Block() | StatementNoVar()) { loopCount.decrementAndGet(); }
+    <WHILE> <LPAREN> Expression() <RPAREN>  { loopCount.incrementAndGet(); }  
(LOOKAHEAD(1) Block() | StatementSink()) { loopCount.decrementAndGet(); }
 }
 
 void DoWhileStatement() : {}
 {
-    <DO> { loopCount.incrementAndGet(); } (LOOKAHEAD(1) Block() | 
StatementNoVar()) <WHILE> <LPAREN> Expression() <RPAREN> { 
loopCount.decrementAndGet(); }
+    <DO> { loopCount.incrementAndGet(); } (LOOKAHEAD(1) Block() | 
StatementSink()) <WHILE> <LPAREN> Expression() <RPAREN> { 
loopCount.decrementAndGet(); }
 }
 
 void ReturnStatement() : {
@@ -551,7 +551,7 @@ void ForeachStatement() : {
     {
         loopCount.incrementAndGet();
     }
-        (LOOKAHEAD(1) Block() | StatementNoVar() )
+        (LOOKAHEAD(1) Block() | StatementSink() )
     {
         loopCount.decrementAndGet();
         jjtThis.setLoopForm(loopForm);
@@ -617,13 +617,19 @@ void SwitchStatement() #SwitchStatement :
 }
 {
     <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY>
-      ( SwitchStatementCase(cases) { helper.defineCase(cases); } )*
+      ( LOOKAHEAD(SwitchStatementLookahead()) ( SwitchStatementCase(cases) { 
helper.defineCase(cases); } )*
+        |
+        { jjtThis.isStatement = false; pushLoop(); } ( 
SwitchExpressionCase(cases) { helper.defineCase(cases); } )*) { popLoop(); }
     <RCURLY>
 {
     helper.defineSwitch(jjtThis);
 }
 }
 
+void SwitchStatementLookahead() #void : {}
+{
+    <CASE> constLiteral() <COLON> | <CASE_DEFAULT> <COLON>
+}
 
 void SwitchStatementCase(SwitchSet cases) #CaseStatement :
 {
@@ -636,7 +642,7 @@ void SwitchStatementCase(SwitchSet cases) #CaseStatement :
         { constants = Collections.singletonList(constant); 
jjtThis.setValues(constants); cases.addAll(constants); } )+
         |
         <CASE_DEFAULT> <COLON> )
-    ( LOOKAHEAD(<LCURLY>) Block() | (StatementNoVar())+ )?
+    ( LOOKAHEAD(<LCURLY>) Block() | (StatementSink() )+ )?
 {
     loopCount.decrementAndGet();
 }
@@ -663,12 +669,10 @@ void SwitchExpressionCase(SwitchSet cases) 
#CaseExpression :
     List<Object> constants = new ArrayList<Object>(1);
 }
 {
-    ( LOOKAHEAD(2)
-        <CASE> ConstLiterals(constants) <LAMBDA> { cases.addAll(constants); 
jjtThis.setValues(constants); }
-        |
-        <CASE_DEFAULT> <LAMBDA>
-    )
-    ( LOOKAHEAD(3) Block() | Expression() (<SEMICOL>)?)
+   ( LOOKAHEAD(2) <CASE> ConstLiterals(constants) <LAMBDA> { 
cases.addAll(constants); jjtThis.setValues(constants); }
+    |
+    <CASE_DEFAULT> <LAMBDA> )
+    ( LOOKAHEAD(3) Block() | Expression() (<SEMICOL>)? )
 }
 
 void ConstLiterals(List<Object> constants) #void :
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 2c415aa4..0c2a89c8 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -112,7 +112,7 @@ public class Issues400Test {
     @Test
     void test402() {
         final JexlContext jc = new MapContext();
-      // @formatter:off
+        // @formatter:off
       final String[] sources = {
         "if (true) { return }",
         "if (true) { 3; return }",
@@ -164,26 +164,26 @@ public class Issues400Test {
         final JexlEngine jexl = new 
JexlBuilder().cache(64).strict(true).safe(false).create();
         Map<String, Object> a = Collections.singletonMap("b", 42);
         // access is constant
-        for (final String src : new String[] { "a.b", "a?.b", "a['b']", 
"a?['b']", "a?.`b`" }) {
+        for (final String src : new String[]{"a.b", "a?.b", "a['b']", 
"a?['b']", "a?.`b`"}) {
             run404(jexl, src, a);
             run404(jexl, src + ";", a);
         }
         // access is variable
-        for (final String src : new String[] { "a[b]", "a?[b]", "a?.`${b}`" }) 
{
+        for (final String src : new String[]{"a[b]", "a?[b]", "a?.`${b}`"}) {
             run404(jexl, src, a, "b");
             run404(jexl, src + ";", a, "b");
         }
         // add a 3rd access
         final Map<String, Object> b = Collections.singletonMap("c", 42);
         a = Collections.singletonMap("b", b);
-        for (final String src : new String[] { "a[b].c", "a?[b]?['c']", 
"a?.`${b}`.c" }) {
+        for (final String src : new String[]{"a[b].c", "a?[b]?['c']", 
"a?.`${b}`.c"}) {
             run404(jexl, src, a, "b");
         }
     }
 
     @Test
     void test404b() {
-      // @formatter:off
+        // @formatter:off
       final JexlEngine jexl = new JexlBuilder()
           .cache(64)
           .strict(true)
@@ -213,13 +213,13 @@ public class Issues400Test {
         // can still do ternary, note the space between ? and [
         script = jexl.createScript("a? ['B']:['C']", "a");
         result = script.execute(null, a);
-        assertArrayEquals(new String[] { "B" }, (String[]) result);
+        assertArrayEquals(new String[]{"B"}, (String[]) result);
         script = jexl.createScript("a?['b'] ?: ['C']", "a");
         result = script.execute(null, a);
         assertEquals(b, result);
         script = jexl.createScript("a?['B'] ?: ['C']", "a");
         result = script.execute(null, a);
-        assertArrayEquals(new String[] { "C" }, (String[]) result);
+        assertArrayEquals(new String[]{"C"}, (String[]) result);
     }
 
     @Test
@@ -252,7 +252,7 @@ public class Issues400Test {
         final JexlScript script0 = jexl.createScript(src0, "x");
         final String src1 = "join(x, '*')";
         final JexlScript script1 = jexl.createScript(src1, "x");
-        for (final Object x : Arrays.asList(Arrays.asList(1, 2, 3, 4), new 
int[] { 1, 2, 3, 4 })) {
+        for (final Object x : Arrays.asList(Arrays.asList(1, 2, 3, 4), new 
int[]{1, 2, 3, 4})) {
             Object result = script0.execute(context, x);
             assertEquals("1*2*3*4", result, src0);
             result = script1.execute(context, x);
@@ -335,8 +335,7 @@ public class Issues400Test {
     void test413d() {
         final JexlBuilder builder = new JexlBuilder().features(new 
JexlFeatures().constCapture(true));
         final JexlEngine jexl = builder.create();
-        final JexlException.Parsing xparse = 
assertThrows(JexlException.Parsing.class, () -> jexl.createScript("var c = 42; 
var f = y -> c += y; f(z)", "z"),
-                "c should be const");
+        final JexlException.Parsing xparse = 
assertThrows(JexlException.Parsing.class, () -> jexl.createScript("var c = 42; 
var f = y -> c += y; f(z)", "z"), "c should be const");
         assertTrue(xparse.getMessage().contains("const"));
     }
 
@@ -465,18 +464,14 @@ public class Issues400Test {
     void test429a() {
         final MapContext ctxt = new MapContext();
         final JexlFeatures features = JexlFeatures.createDefault();
-        final JexlEngine jexl = new JexlBuilder()
-                .features(features)
-                .safe(false).strict(true).silent(false).create();
+        final JexlEngine jexl = new 
JexlBuilder().features(features).safe(false).strict(true).silent(false).create();
         final JexlScript f = jexl.createScript("x -> x");
         ctxt.set("f", f);
-        String src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
-                +"b ? b : f(2);";
+        String src = "#pragma jexl.namespace.b " + Ns429.class.getName() + 
"\n" + "b ? b : f(2);";
         JexlScript script = jexl.createScript(src, "b");
         assertEquals(1, (int) script.execute(ctxt, 1));
 
-        src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
-                +"b ? b:f(2) : 1;";
+        src = "#pragma jexl.namespace.b " + Ns429.class.getName() + "\n" + "b 
? b:f(2) : 1;";
         script = jexl.createScript(src, "b");
         assertEquals(20042, (int) script.execute(ctxt, 1));
     }
@@ -487,18 +482,14 @@ public class Issues400Test {
         ctxt.set("b", 1);
         final JexlFeatures features = JexlFeatures.createDefault();
         features.namespaceIdentifier(true);
-        final JexlEngine jexl = new JexlBuilder()
-                .features(features)
-                .safe(false).strict(true).silent(false).create();
+        final JexlEngine jexl = new 
JexlBuilder().features(features).safe(false).strict(true).silent(false).create();
         final JexlScript f = jexl.createScript("x -> x");
         ctxt.set("f", f);
-        String src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
-                +"b ? b : f(2);";
+        String src = "#pragma jexl.namespace.b " + Ns429.class.getName() + 
"\n" + "b ? b : f(2);";
         JexlScript script = jexl.createScript(src);
         assertEquals(1, (int) script.execute(ctxt));
 
-        src = "#pragma jexl.namespace.b "+Ns429.class.getName()  +"\n"
-                +"b ? b:f(2) : 1;";
+        src = "#pragma jexl.namespace.b " + Ns429.class.getName() + "\n" + "b 
? b:f(2) : 1;";
         script = jexl.createScript(src);
         assertEquals(20042, (int) script.execute(ctxt));
     }
@@ -534,7 +525,7 @@ public class Issues400Test {
         try {
             final JexlScript script = jexl.createScript(src);
             fail("xx is already defined in scope");
-        } catch(final JexlException.Parsing parsing) {
+        } catch (final JexlException.Parsing parsing) {
             assertTrue(parsing.getDetail().contains("xx"));
         }
     }
@@ -567,6 +558,7 @@ public class Issues400Test {
         public Arithmetic435(final boolean strict) {
             super(strict);
         }
+
         public Object empty(final String type) {
             if ("list".equals(type)) {
                 return Collections.emptyList();
@@ -588,13 +580,13 @@ public class Issues400Test {
 
     @Test
     void test436a() {
-        final String[] srcs = {"let i = null; ++i", "let i; ++i;", "let i; 
i--;",  "let i; i++;"};
+        final String[] srcs = {"let i = null; ++i", "let i; ++i;", "let i; 
i--;", "let i; i++;"};
         run436(null, srcs);
     }
 
     @Test
     void test436b() {
-        final String[] srcs = {"var i = null; ++i", "var i; ++i;", "var i; 
i--;",  "var i; i++;"};
+        final String[] srcs = {"var i = null; ++i", "var i; ++i;", "var i; 
i--;", "var i; i++;"};
         run436(null, srcs);
     }
 
@@ -602,13 +594,13 @@ public class Issues400Test {
     void test436c() {
         final JexlContext ctxt = new MapContext();
         ctxt.set("i", null);
-        final String[] srcs = {"++i", "++i;", "i--;",  "i++;"};
+        final String[] srcs = {"++i", "++i;", "i--;", "i++;"};
         run436(null, srcs);
     }
 
     void run436(final JexlContext ctxt, final String[] srcs) {
         final JexlEngine jexl = new JexlBuilder().create();
-        for(final String src : srcs) {
+        for (final String src : srcs) {
             final JexlScript script = jexl.createScript(src);
             assertThrows(JexlException.Operator.class, () -> 
script.execute(ctxt));
         }
@@ -653,8 +645,11 @@ public class Issues400Test {
         assertEquals(2, values.size());
     }
 
-    /** The set of characters that may be followed by a '='.*/
+    /**
+     * The set of characters that may be followed by a '='.
+     */
     static final char[] EQ_FRIEND;
+
     static {
         final char[] eq = {'!', ':', '<', '>', '^', '|', '&', '+', '-', '/', 
'*', '~', '='};
         Arrays.sort(eq);
@@ -663,6 +658,7 @@ public class Issues400Test {
 
     /**
      * Transcodes a SQL-inspired expression to a JEXL expression.
+     *
      * @param expr the expression to transcode
      * @return the resulting expression
      */
@@ -735,9 +731,9 @@ public class Issues400Test {
 
     @Test
     void testSQLTranspose() {
-        final String[] e = { "a<>b", "a = 2", "a.b.c <> '1<>0'" };
-        final String[] j = { "a!=b", "a == 2", "a.b.c != '1<>0'" };
-        for(int i = 0; i < e.length; ++i) {
+        final String[] e = {"a<>b", "a = 2", "a.b.c <> '1<>0'"};
+        final String[] j = {"a!=b", "a == 2", "a.b.c != '1<>0'"};
+        for (int i = 0; i < e.length; ++i) {
             final String je = transcodeSQLExpr(e[i]);
             Assertions.assertEquals(j[i], je);
         }
@@ -745,7 +741,7 @@ public class Issues400Test {
 
     @Test
     void testSQLNoChange() {
-        final String[] e = { "a <= 2", "a >= 2", "a := 2", "a + 3 << 4 > 5",  
};
+        final String[] e = {"a <= 2", "a >= 2", "a := 2", "a + 3 << 4 > 5",};
         for (final String element : e) {
             final String je = transcodeSQLExpr(element);
             Assertions.assertEquals(element, je);
@@ -754,12 +750,7 @@ public class Issues400Test {
 
     @Test
     void test438() {// no local, no lambda, no loops, no-side effects
-        final JexlFeatures f = new JexlFeatures()
-                .localVar(false)
-                .lambda(false)
-                .loops(false)
-                .sideEffect(false)
-                .sideEffectGlobal(false);
+        final JexlFeatures f = new 
JexlFeatures().localVar(false).lambda(false).loops(false).sideEffect(false).sideEffectGlobal(false);
         final JexlBuilder builder = new 
JexlBuilder().parserFactory(SQLParser::new).cache(32).features(f);
         final JexlEngine sqle = builder.create();
         Assertions.assertTrue((boolean) sqle.createScript("a <> 25", 
"a").execute(null, 24));
@@ -811,7 +802,7 @@ public class Issues400Test {
         final Object result = script.execute(null, "a", "b");
         Assertions.assertEquals("a\n?= ba\n?== b", result);
 
-        final String TEST447 =  "src/test/scripts/test447.jexl";
+        final String TEST447 = "src/test/scripts/test447.jexl";
         final File src447 = new File(TEST447);
         final JexlScript script447 = jexl.createScript(src447);
         final Object result447 = script447.execute(null);
@@ -822,5 +813,6 @@ public class Issues400Test {
             Assertions.assertTrue(item);
         }
     }
+
 }
 
diff --git a/src/test/java/org/apache/commons/jexl3/SwitchTest.java 
b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
index a66f1cc8..9b220319 100644
--- a/src/test/java/org/apache/commons/jexl3/SwitchTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
@@ -21,9 +21,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import org.apache.commons.jexl3.internal.Debugger;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests switch/case statement and expression.
  */
@@ -47,13 +51,13 @@ public class SwitchTest extends JexlTestCase {
   void testBrokenSwitchExpression0() {
     final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
     try {
-      final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 
return 2;  }; j", "i");
+      jexl.createScript("var j = switch(i) { case 1 -> return 2;  }; j", "i");
       fail("should not be able to create script with return in switch 
expression");
     } catch (final JexlException.Parsing xparse) {
       assertTrue(xparse.getMessage().contains("return"));
     }
     try {
-      final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 
break; }; j", "i");
+      jexl.createScript("var j = switch(i) { case 1 -> break; }; j", "i");
       fail("should not be able to create script with break in switch 
expression");
     } catch (final JexlException.Parsing xparse) {
       assertTrue(xparse.getMessage().contains("break"));
@@ -104,7 +108,13 @@ public class SwitchTest extends JexlTestCase {
   void test440b() {
     final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
     final String src =
-            "switch (x) { case 10 : return 3; case 20 : case 21 : return 4; 
case 32: break; default : return x + 4; } 169";
+      "switch (x) {\n" +
+        " case 10 : return 3\n;" +
+        " case 20 : case 21 : return 4;\n" +
+        " case 32: break; \n" +
+        " default : return x + 4;\n" +
+      " }\n" +
+      " 169";
     final JexlScript script = jexl.createScript(src, "x");
     assertNotNull(script);
     final String dbgStr = script.getParsedText();
@@ -171,4 +181,75 @@ public class SwitchTest extends JexlTestCase {
       assertTrue(xjexl.getMessage().contains("switch"));
     }
   }
+
+  @Test
+  void testSwitchStatement1() {
+    // a one statement script
+    String src = "switch (x) {\n"
+            + "  case 1: return 'one';\n"
+            + "  case 2: return 'two';\n"
+            + "  case 3: return 'three';\n"
+            + "  default: return 'many';\n"
+            + "}";
+    runSwitch(src);
+    String src2 = "if (true) { " + src + " }";
+    runSwitch(src2);
+  }
+
+  @Test
+  void testSwitchExpression1() {
+    // a one statement script that uses the expression syntax
+    String src = "switch (x) {\n"
+            + "  case 1 -> 'one';\n"
+            + "  case 2 -> 'two';\n"
+            + "  case 3 -> 'three';\n"
+            + "  default -> 'many';\n"
+            + "}";
+    runSwitch(src);
+    String src2 = "if (true) { " + src + " }";
+    runSwitch(src2);
+  }
+
+  void runSwitch(final String src) {
+    JexlEngine jexl = new JexlBuilder().create();
+    JexlScript script = jexl.createScript(src, "x");
+    Object result;
+    result = script.execute(null, 1);
+    Assertions.assertEquals("one", result);
+    result = script.execute(null, 2);
+    Assertions.assertEquals("two", result);
+    result = script.execute(null, 3);
+    Assertions.assertEquals("three", result);
+    result = script.execute(null, 4);
+    Assertions.assertEquals("many", result);
+    result = script.execute(null, 42);
+    Assertions.assertEquals("many", result);
+    final Debugger debugger = new Debugger();
+    assertTrue(debugger.debug(script));
+    final String dbgStr = debugger.toString();
+    assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbgStr));
+  }
+
+  @Test
+  void testSwitchExpressionFail() {
+    List<String> err = Arrays.asList("break", "continue");
+    for (String keyword : err) {
+      switchExpressionFailKeyword(keyword);
+    }
+  }
+  private void switchExpressionFailKeyword(String keyword) {
+    // a one statement script that uses the expression syntax
+    String src = "switch (x) {\n"
+            + "  case 1 -> 'one';\n"
+            + "  case 2 -> " + keyword + ";\n"
+            + "  default -> 'many';\n"
+            + "}";
+    JexlEngine jexl = new JexlBuilder().create();
+    try {
+      jexl.createScript(src, "x");
+      fail("should not be able to create script with " + keyword + " in switch 
expression");
+    } catch (final JexlException.Parsing xparse) {
+      assertTrue(xparse.getMessage().contains(keyword));
+    }
+  }
 }


Reply via email to