You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/05/22 07:30:02 UTC

[groovy] 01/01: GROOVY-9272: Support switch expression

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

sunlan pushed a commit to branch GROOVY-9272
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit ef1cf6df07018aa1310c194605b8edea205fd819
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat May 22 13:49:17 2021 +0800

    GROOVY-9272: Support switch expression
---
 src/antlr/GroovyLexer.g4                           |   1 +
 src/antlr/GroovyParser.g4                          |  32 +++
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 257 ++++++++++++++++++++-
 .../groovy/parser/antlr4/SemanticPredicates.java   |   3 +-
 .../codehaus/groovy/ast/stmt/YieldStatement.java   |  20 ++
 .../core/SwitchExpression_01x.groovy               |  25 ++
 .../core/SwitchExpression_02x.groovy               |  30 +++
 .../core/SwitchExpression_03x.groovy               |  25 ++
 .../core/SwitchExpression_04x.groovy               |  26 +++
 .../core/SwitchExpression_05x.groovy               |  24 ++
 .../core/SwitchExpression_06x.groovy               |  30 +++
 .../core/SwitchExpression_07x.groovy               |  39 ++++
 .../core/SwitchExpression_08x.groovy               |  36 +++
 .../core/SwitchExpression_09x.groovy               |  29 +++
 .../core/SwitchExpression_10x.groovy               |  59 +++++
 .../core/SwitchExpression_11x.groovy               | 102 ++++++++
 .../core/SwitchExpression_12x.groovy               |  32 +++
 .../core/SwitchExpression_13x.groovy               |  25 ++
 .../fail/SwitchExpression_01x.groovy               |  24 ++
 .../fail/SwitchExpression_02x.groovy               |  24 ++
 .../fail/SwitchExpression_03x.groovy               |  25 ++
 .../fail/SwitchExpression_04x.groovy               |  25 ++
 .../groovy/parser/antlr4/GroovyParserTest.groovy   |  36 ++-
 .../groovy/parser/antlr4/SyntaxErrorTest.groovy    |   9 +-
 .../apache/groovy/parser/antlr4/TestUtils.groovy   |   5 +-
 25 files changed, 928 insertions(+), 15 deletions(-)

diff --git a/src/antlr/GroovyLexer.g4 b/src/antlr/GroovyLexer.g4
index 6eda984..1d1b9d8 100644
--- a/src/antlr/GroovyLexer.g4
+++ b/src/antlr/GroovyLexer.g4
@@ -408,6 +408,7 @@ fragment
 BOOLEAN       : 'boolean';
 
 BREAK         : 'break';
+YIELD         : 'yield';
 
 fragment
 BYTE          : 'byte';
diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index f971d33..d19f019 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -45,6 +45,7 @@ options {
 }
 
 @members {
+    private boolean inSwitchExpression = false;
 
     public static class GroovyParserRuleContext extends ParserRuleContext implements NodeMetaDataHandler {
         private Map metaDataMap = null;
@@ -614,6 +615,11 @@ breakStatement
         identifier?
     ;
 
+yieldStatement
+    :   YIELD
+        expression
+    ;
+
 tryCatchStatement
     :   TRY resources? nls block
         (nls catchClause)*
@@ -634,6 +640,8 @@ statement
     |   THROW expression                                                                                    #throwStmtAlt
     |   breakStatement                                                                                      #breakStmtAlt
     |   continueStatement                                                                                   #continueStmtAlt
+    |   { inSwitchExpression }?
+        yieldStatement                                                                                      #yieldStmtAlt
     |   identifier COLON nls statement                                                                      #labeledStmtAlt
     |   assertStatement                                                                                     #assertStmtAlt
     |   localVariableDeclaration                                                                            #localVariableDeclarationStmtAlt
@@ -737,6 +745,26 @@ postfixExpression
     :   pathExpression op=(INC | DEC)?
     ;
 
+switchExpression
+@init {
+    inSwitchExpression = true;
+}
+@after {
+    inSwitchExpression = false;
+}
+    :   SWITCH expressionInPar nls LBRACE nls switchBlockStatementExpressionGroup* nls RBRACE
+    ;
+
+switchBlockStatementExpressionGroup
+    :   (switchExpressionLabel nls)+ blockStatements
+    ;
+
+switchExpressionLabel
+    :   (   CASE expressionList[true]
+        |   DEFAULT
+        ) ac=(ARROW | COLON)
+    ;
+
 expression
     // must come before postfixExpression to resovle the ambiguities between casting and call on parentheses expression, e.g. (int)(1 / 2)
     :   castParExpression castOperandExpression                                             #castExprAlt
@@ -753,6 +781,8 @@ expression
     // ++(prefix)/--(prefix)/+(unary)/-(unary) (level 3)
     |   op=(INC | DEC | ADD | SUB) expression                                               #unaryAddExprAlt
 
+    |   switchExpression                                                                    #switchExprAlt
+
     // multiplication/division/modulo (level 4)
     |   left=expression nls op=(MUL | DIV | MOD) nls right=expression                       #multiplicativeExprAlt
 
@@ -1177,6 +1207,7 @@ identifier
 //  |   DEF
     |   TRAIT
     |   AS
+    |   YIELD
     ;
 
 builtInType
@@ -1229,6 +1260,7 @@ keywords
     |   VAR
     |   VOLATILE
     |   WHILE
+    |   YIELD
 
     |   NullLiteral
     |   BooleanLiteral
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index 9801be9..55ff1a3 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -283,6 +283,7 @@ import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
 import org.codehaus.groovy.ast.stmt.ThrowStatement;
 import org.codehaus.groovy.ast.stmt.TryCatchStatement;
 import org.codehaus.groovy.ast.stmt.WhileStatement;
+import org.codehaus.groovy.ast.stmt.YieldStatement;
 import org.codehaus.groovy.ast.tools.ClosureUtils;
 import org.codehaus.groovy.classgen.Verifier;
 import org.codehaus.groovy.control.CompilationFailedException;
@@ -314,6 +315,13 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static groovy.lang.Tuple.tuple;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.ARROW;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.SwitchBlockStatementExpressionGroupContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.SwitchExprAltContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.SwitchExpressionContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.SwitchExpressionLabelContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.YieldStatementContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.YieldStmtAltContext;
 import static org.apache.groovy.parser.antlr4.GroovyParser.ADD;
 import static org.apache.groovy.parser.antlr4.GroovyParser.AS;
 import static org.apache.groovy.parser.antlr4.GroovyParser.CASE;
@@ -339,13 +347,18 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.STATIC;
 import static org.apache.groovy.parser.antlr4.GroovyParser.SUB;
 import static org.apache.groovy.parser.antlr4.GroovyParser.VAR;
 import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureAST;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.closureX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.classgen.asm.util.TypeUtil.isPrimitiveType;
-import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
 import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;
+import static org.codehaus.groovy.syntax.Token.newSymbol;
 
 /**
  * Builds the AST from the parse tree generated by Antlr4.
@@ -1045,6 +1058,16 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
 
     @Override
+    public YieldStatement visitYieldStatement(YieldStatementContext ctx) {
+        return configureAST(new YieldStatement((Expression) this.visit(ctx.expression())), ctx);
+    }
+
+    @Override
+    public YieldStatement visitYieldStmtAlt(YieldStmtAltContext ctx) {
+        return configureAST(this.visitYieldStatement(ctx.yieldStatement()), ctx);
+    }
+
+    @Override
     public ContinueStatement visitContinueStatement(final ContinueStatementContext ctx) {
         if (visitingLoopStatementCount == 0) {
             throw createParsingFailedException("continue statement is only allowed inside loops", ctx);
@@ -1058,6 +1081,237 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
 
     }
 
+    @Override
+    public Expression visitSwitchExprAlt(SwitchExprAltContext ctx) {
+        return configureAST(this.visitSwitchExpression(ctx.switchExpression()), ctx);
+    }
+
+    /**
+     * <pre>
+     * switch(a) {
+     *     case 0, 1  ->   'a';
+     *     case 2     ->   'b';
+     *     default    ->   'z';
+     * }
+     * </pre>
+     * the above code will be transformed to:
+     * <pre>
+     * {->
+     *     def switchExprResult467859974288800 = null
+     *     switch(a) {
+     *         case 0:
+     *         case 1:  switchExprResult467859974288800 = 'a'; break;
+     *         case 2:  switchExprResult467859974288800 = 'b'; break;
+     *         default: switchExprResult467859974288800 = 'z'; break;
+     *     }
+     *     return switchExprResult467859974288800
+     * }()
+     * </pre>
+     *
+     * @param ctx the parse tree
+     * @return {@link MethodCallExpression} instance
+     */
+    @Override
+    public MethodCallExpression visitSwitchExpression(SwitchExpressionContext ctx) {
+        final VariableExpression switchExprResultVariableExpression = new VariableExpression("switchExprResult" + System.nanoTime(), ClassHelper.DYNAMIC_TYPE);
+        ExpressionStatement switchExprResultDeclarationStatement =
+                new ExpressionStatement(
+                        new DeclarationExpression(
+                                switchExprResultVariableExpression,
+                                newSymbol(Types.ASSIGN, -1, -1),
+                                new ConstantExpression(null)
+                        )
+                );
+
+        validateSwitchExpressionLabels(ctx);
+
+        List<Statement> statementList =
+                ctx.switchBlockStatementExpressionGroup().stream()
+                        .map(e -> {
+                            e.putNodeMetaData(SWITCH_EXPRESSION_RESULT_VARIABLE_EXPRESSION, switchExprResultVariableExpression);
+                            return this.visitSwitchBlockStatementExpressionGroup(e);
+                        })
+                        .reduce(new LinkedList<>(), (r, e) -> {
+                            r.addAll(e);
+                            return r;
+                        });
+
+        List<CaseStatement> caseStatementList = new LinkedList<>();
+        List<Statement> defaultStatementList = new LinkedList<>();
+
+        statementList.forEach(e -> {
+            if (e instanceof CaseStatement) {
+                caseStatementList.add((CaseStatement) e);
+            } else if (isTrue(e, IS_SWITCH_DEFAULT)) {
+                defaultStatementList.add(e);
+            }
+        });
+
+        int defaultStatementListSize = defaultStatementList.size();
+        if (defaultStatementListSize > 1) {
+            throw createParsingFailedException("switch expression should have only one default case, which should appear at last", defaultStatementList.get(0));
+        }
+
+        if (defaultStatementListSize > 0 && last(statementList) instanceof CaseStatement) {
+            throw createParsingFailedException("default case should appear at last", defaultStatementList.get(0));
+        }
+
+        SwitchStatement switchStatement = configureAST(
+                new SwitchStatement(
+                        this.visitExpressionInPar(ctx.expressionInPar()),
+                        caseStatementList,
+                        defaultStatementListSize == 0 ? EmptyStatement.INSTANCE : defaultStatementList.get(0)
+                ),
+                ctx);
+
+        return configureAST(
+                callX(
+                        configureAST(
+                                closureX(
+                                        null,
+                                        createBlockStatement(
+                                                switchExprResultDeclarationStatement,
+                                                switchStatement,
+                                                returnS(
+                                                        varX(switchExprResultVariableExpression.getName())
+                                                )
+                                        )
+                                ),
+                                ctx
+                        ),
+                        "call"
+                ),
+                ctx
+        );
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<Statement> visitSwitchBlockStatementExpressionGroup(SwitchBlockStatementExpressionGroupContext ctx) {
+        VariableExpression switchExprResultVariableExpression = ctx.getNodeMetaData(SWITCH_EXPRESSION_RESULT_VARIABLE_EXPRESSION);
+        final String variableName = switchExprResultVariableExpression.getName();
+
+        int labelCnt = ctx.switchExpressionLabel().size();
+        List<Token> firstLabelHolder = new ArrayList<>(1);
+        final int[] arrowCntHolder = new int[1];
+
+        return (List<Statement>) ctx.switchExpressionLabel().stream()
+                .map(e -> (Object) this.visitSwitchExpressionLabel(e))
+                .reduce(new ArrayList<Statement>(4), (r, e) -> {
+                    List<Statement> statementList = (List<Statement>) r;
+                    Tuple3<Token, List<Expression>, Integer> tuple = (Tuple3<Token, List<Expression>, Integer>) e;
+
+                    if (ARROW == tuple.getV3()) {
+                        if (++arrowCntHolder[0] > 1 && !firstLabelHolder.isEmpty()) {
+                            throw createParsingFailedException("`case ... ->` does not support falling through cases", firstLabelHolder.get(0));
+                        }
+                    }
+
+                    boolean isLast = labelCnt - 1 == statementList.size();
+
+                    BlockStatement codeBlock = this.visitBlockStatements(ctx.blockStatements());
+                    List<Statement> statements = codeBlock.getStatements();
+                    int statementsCnt = statements.size();
+                    if (0 == statementsCnt) {
+                        throw createParsingFailedException("`yield` statement is expected", ctx.blockStatements());
+                    }
+
+                    if (statementsCnt > 1) {
+                        throw new GroovyBugError("statements count should be 1, but the count is actually: " + statementsCnt);
+                    }
+
+                    final Statement exprOrBlockStatement = statements.get(0);
+                    if (exprOrBlockStatement instanceof BlockStatement) {
+                        codeBlock = (BlockStatement) exprOrBlockStatement;
+                        statements = codeBlock.getStatements();
+                    }
+
+                    Expression resultExpression;
+                    Statement lastStatement = statements.remove(statements.size() - 1);
+                    if (lastStatement instanceof YieldStatement) {
+                        resultExpression = ((YieldStatement) lastStatement).getValueExpression();
+                    } else if (lastStatement instanceof ExpressionStatement) {
+                        resultExpression = ((ExpressionStatement) lastStatement).getExpression();
+                    } else {
+                        throw createParsingFailedException("The last statement should be a `yield` statement or an expression", lastStatement);
+                    }
+
+                    appendStatementsToBlockStatement(
+                            codeBlock,
+
+                            configureAST(
+                                    assignS(
+                                            new VariableExpression(variableName),
+                                            resultExpression
+                                    ),
+                                    resultExpression
+                            ),
+                            new BreakStatement(null)
+                    );
+
+                    switch (tuple.getV1().getType()) {
+                        case CASE: {
+                            if (!asBoolean(statementList)) {
+                                firstLabelHolder.add(tuple.getV1());
+                            }
+
+                            for (int i = 0, n = tuple.getV2().size(); i < n; i++) {
+                                Expression expr = tuple.getV2().get(i);
+
+                                statementList.add(
+                                        configureAST(
+                                                new CaseStatement(
+                                                        expr,
+
+                                                        // check whether processing the last label. if yes, block statement should be attached.
+                                                        (isLast && i == n - 1) ? codeBlock
+                                                                : EmptyStatement.INSTANCE
+                                                ),
+                                                firstLabelHolder.get(0)));
+                            }
+
+                            break;
+                        }
+                        case DEFAULT: {
+                            codeBlock.putNodeMetaData(IS_SWITCH_DEFAULT, true);
+
+                            statementList.add(
+                                    // this.configureAST(codeBlock, tuple.getKey())
+                                    codeBlock
+                            );
+
+                            break;
+                        }
+                    }
+
+                    return statementList;
+                });
+    }
+
+    private void validateSwitchExpressionLabels(SwitchExpressionContext ctx) {
+        Map<String, List<SwitchExpressionLabelContext>> acMap =
+                ctx.switchBlockStatementExpressionGroup().stream()
+                        .flatMap(e -> e.switchExpressionLabel().stream())
+                        .collect(Collectors.groupingBy(e -> e.ac.getText()));
+        if (acMap.size() > 1) {
+            List<SwitchExpressionLabelContext> lastSelcList = acMap.values().stream().reduce((prev, next) -> next).orElse(null);
+            throw createParsingFailedException(acMap.keySet().stream().collect(Collectors.joining("` and `", "`", "`")) + " cannot be used together", lastSelcList.get(0).ac);
+        }
+    }
+
+    @Override
+    public Tuple3<Token, List<Expression>, Integer> visitSwitchExpressionLabel(SwitchExpressionLabelContext ctx) {
+        final Integer acType = ctx.ac.getType();
+        if (asBoolean(ctx.CASE())) {
+            return tuple(ctx.CASE().getSymbol(), this.visitExpressionList(ctx.expressionList()), acType);
+        } else if (asBoolean(ctx.DEFAULT())) {
+            return tuple(ctx.DEFAULT().getSymbol(), Collections.singletonList(EmptyExpression.INSTANCE), acType);
+        }
+
+        throw createParsingFailedException("Unsupported switch expression label: " + ctx.getText(), ctx);
+    }
+
+
     // } statement -------------------------------------------------------------
 
     @Override
@@ -4726,4 +4980,5 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     private static final String INTEGER_LITERAL_TEXT = "_INTEGER_LITERAL_TEXT";
     private static final String FLOATING_POINT_LITERAL_TEXT = "_FLOATING_POINT_LITERAL_TEXT";
     private static final String ENCLOSING_INSTANCE_EXPRESSION = "_ENCLOSING_INSTANCE_EXPRESSION";
+    private static final String SWITCH_EXPRESSION_RESULT_VARIABLE_EXPRESSION = "_SWITCH_EXPRESSION_RESULT_VARIABLE_EXPRESSION";
 }
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java b/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java
index 78e5877..addfd47 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java
@@ -42,6 +42,7 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.PathExpressionContext
 import static org.apache.groovy.parser.antlr4.GroovyParser.PostfixExprAltContext;
 import static org.apache.groovy.parser.antlr4.GroovyParser.PostfixExpressionContext;
 import static org.apache.groovy.parser.antlr4.GroovyParser.StringLiteral;
+import static org.apache.groovy.parser.antlr4.GroovyParser.YIELD;
 import static org.apache.groovy.parser.antlr4.util.StringUtils.matches;
 
 /**
@@ -141,7 +142,7 @@ public class SemanticPredicates {
     public static boolean isInvalidMethodDeclaration(TokenStream ts) {
         int tokenType = ts.LT(1).getType();
 
-        return (Identifier == tokenType || CapitalizedIdentifier == tokenType || StringLiteral == tokenType)
+        return (Identifier == tokenType || CapitalizedIdentifier == tokenType || StringLiteral == tokenType || YIELD == tokenType)
                 && LPAREN == (ts.LT(2).getType());
     }
 
diff --git a/src/main/java/org/codehaus/groovy/ast/stmt/YieldStatement.java b/src/main/java/org/codehaus/groovy/ast/stmt/YieldStatement.java
new file mode 100644
index 0000000..f59d022
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/ast/stmt/YieldStatement.java
@@ -0,0 +1,20 @@
+package org.codehaus.groovy.ast.stmt;
+
+import org.codehaus.groovy.ast.expr.Expression;
+
+/**
+ * Represents a break statement in a switch expression
+ *
+ * @since 4.0.0
+ */
+public class YieldStatement extends Statement {
+    private Expression valueExpression;
+
+    public YieldStatement(Expression valueExpression) {
+        this.valueExpression = valueExpression;
+    }
+
+    public Expression getValueExpression() {
+        return valueExpression;
+    }
+}
\ No newline at end of file
diff --git a/src/test-resources/core/SwitchExpression_01x.groovy b/src/test-resources/core/SwitchExpression_01x.groovy
new file mode 100644
index 0000000..448719e
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_01x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 8 -> 'b'
+    default -> 'z'
+}
+assert 'z' == result
diff --git a/src/test-resources/core/SwitchExpression_02x.groovy b/src/test-resources/core/SwitchExpression_02x.groovy
new file mode 100644
index 0000000..5574d94
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_02x.groovy
@@ -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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6 -> 'a'
+}
+assert 'a' == result
+
+a = 9
+result = switch(a) {
+    case 6 -> 'a'
+}
+assert null == result
diff --git a/src/test-resources/core/SwitchExpression_03x.groovy b/src/test-resources/core/SwitchExpression_03x.groovy
new file mode 100644
index 0000000..ef46adb
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_03x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6, 8 -> 'b'
+    default -> 'z'
+}
+assert 'b' == result
diff --git a/src/test-resources/core/SwitchExpression_04x.groovy b/src/test-resources/core/SwitchExpression_04x.groovy
new file mode 100644
index 0000000..8b773cb
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_04x.groovy
@@ -0,0 +1,26 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 9
+def result = switch(a) {
+    case 6, 8 -> 'b'
+    case 9 -> 'c'
+    default -> 'z'
+}
+assert 'c' == result
diff --git a/src/test-resources/core/SwitchExpression_05x.groovy b/src/test-resources/core/SwitchExpression_05x.groovy
new file mode 100644
index 0000000..5a52c93
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_05x.groovy
@@ -0,0 +1,24 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6, 8 -> 1 + 2
+}
+assert 3 == result
diff --git a/src/test-resources/core/SwitchExpression_06x.groovy b/src/test-resources/core/SwitchExpression_06x.groovy
new file mode 100644
index 0000000..f864a24
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_06x.groovy
@@ -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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6, 8 -> 1.plus 2
+}
+assert 3 == result
+
+a = 8
+result = switch(a) {
+    case 6, 8 -> 1.plus 2
+}
+assert 3 == result
diff --git a/src/test-resources/core/SwitchExpression_07x.groovy b/src/test-resources/core/SwitchExpression_07x.groovy
new file mode 100644
index 0000000..07e375b
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_07x.groovy
@@ -0,0 +1,39 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 9
+def result = switch(a) {
+    case 6, 8 -> 1.plus 2
+    default -> 1.plus 5
+}
+assert 6 == result
+
+a = 6
+result = switch(a) {
+    case 6, 8 -> 1.plus 2
+    default -> 1.plus 5
+}
+assert 3 == result
+
+a = 8
+result = switch(a) {
+    case 6, 8 -> 1.plus 2
+    default -> 1.plus 5
+}
+assert 3 == result
diff --git a/src/test-resources/core/SwitchExpression_08x.groovy b/src/test-resources/core/SwitchExpression_08x.groovy
new file mode 100644
index 0000000..0caf247
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_08x.groovy
@@ -0,0 +1,36 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6 -> { yield 'a' }
+}
+assert 'a' == result
+
+a = 8
+result = switch(a) {
+    case 6, 8 -> { yield 'a' }
+}
+assert 'a' == result
+
+a = 9
+result = switch(a) {
+    case 6, 8 -> { yield 'a' }
+}
+assert null == result
diff --git a/src/test-resources/core/SwitchExpression_09x.groovy b/src/test-resources/core/SwitchExpression_09x.groovy
new file mode 100644
index 0000000..14e0dd0
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_09x.groovy
@@ -0,0 +1,29 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 9
+def result = switch(a) {
+    case 6 -> { yield 'a' }
+    default -> { yield 'z' }
+}
+assert 'z' == result
+
+
+
+
diff --git a/src/test-resources/core/SwitchExpression_10x.groovy b/src/test-resources/core/SwitchExpression_10x.groovy
new file mode 100644
index 0000000..450fb24
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_10x.groovy
@@ -0,0 +1,59 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6 -> {
+        def n = 1
+        yield 'a' + n
+    }
+    default -> { yield 'z' }
+}
+assert 'a1' == result
+
+a = 8
+result = switch(a) {
+    case 6, 8 -> {
+        def n = 1
+        yield 'a' + n
+    }
+    default -> { yield 'z' }
+}
+assert 'a1' == result
+
+a = 9
+result = switch(a) {
+    case 6, 8 -> {
+        def n = 1
+        yield 'a' + n
+    }
+    default -> { yield 'z' }
+}
+assert 'z' == result
+
+a = 9
+result = switch(a) {
+    case 6, 8 -> {
+        def n = 1
+        yield 'a' + n
+    }
+    default -> yield 'z'
+}
+assert 'z' == result
+
diff --git a/src/test-resources/core/SwitchExpression_11x.groovy b/src/test-resources/core/SwitchExpression_11x.groovy
new file mode 100644
index 0000000..4c0c905
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_11x.groovy
@@ -0,0 +1,102 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6 : yield 'a'
+}
+assert 'a' == result
+
+a = 8
+result = switch(a) {
+    case 6 : yield 'a'
+    default: yield 'b'
+}
+assert 'b' == result
+
+a = 9
+result = switch(a) {
+    case 6, 9 : yield 'a'
+    default: yield 'b'
+}
+assert 'a' == result
+
+a = 7
+result = switch(a) {
+    case 7:
+    case 6, 9 : yield 'a'
+    default: yield 'b'
+}
+assert 'a' == result
+
+
+a = 10
+result = switch(a) {
+    case 7:
+    case 6, 9 : yield 'a'
+    case 10:
+    case 11: yield 'c'
+    default: yield 'b'
+}
+assert 'c' == result
+
+a = 10
+result = switch(a) {
+    case 7:
+    case 6, 9 : yield 'a'
+    case 10:
+    case 11: {
+            def x = 1
+            yield 'c' + x
+        }
+    default: yield 'b'
+}
+assert 'c1' == result
+
+
+a = 6
+result = switch(a) {
+    case 6 : 'a'
+}
+assert 'a' == result
+
+a = 6
+result = switch(a) {
+    default : 'b'
+}
+assert 'b' == result
+
+a = 6
+result = switch(a) {
+    case 6 : { 'a' }
+}
+assert 'a' == result
+
+a = 6
+result = switch(a) {
+    default : { 'b' }
+}
+assert 'b' == result
+
+a = 6
+result = switch(a) {
+    case 6  : 'a'
+    default : 'b'
+}
+assert 'a' == result
diff --git a/src/test-resources/core/SwitchExpression_12x.groovy b/src/test-resources/core/SwitchExpression_12x.groovy
new file mode 100644
index 0000000..bbe8342
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_12x.groovy
@@ -0,0 +1,32 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+def result = switch(a) {
+    case 6 -> 'a'
+    default-> yield 'b'
+}
+assert 'a' == result
+
+a = 6
+result = switch(a) {
+    case 6 -> 'a'
+    default -> yield 'b'
+}
+assert 'a' == result
diff --git a/src/test-resources/core/SwitchExpression_13x.groovy b/src/test-resources/core/SwitchExpression_13x.groovy
new file mode 100644
index 0000000..4a2a7c8
--- /dev/null
+++ b/src/test-resources/core/SwitchExpression_13x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def yield (String msg) { return msg }
+def yield () { return 'b' }
+
+def result = yield 'a'
+assert 'a' == result
+assert 'b'== yield()
diff --git a/src/test-resources/fail/SwitchExpression_01x.groovy b/src/test-resources/fail/SwitchExpression_01x.groovy
new file mode 100644
index 0000000..df57b27
--- /dev/null
+++ b/src/test-resources/fail/SwitchExpression_01x.groovy
@@ -0,0 +1,24 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+switch(a) {
+    case 8 ->
+    case 9 -> 'a'
+}
diff --git a/src/test-resources/fail/SwitchExpression_02x.groovy b/src/test-resources/fail/SwitchExpression_02x.groovy
new file mode 100644
index 0000000..8112793
--- /dev/null
+++ b/src/test-resources/fail/SwitchExpression_02x.groovy
@@ -0,0 +1,24 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+switch(a) {
+    case 8 ->
+    default -> 'b'
+}
diff --git a/src/test-resources/fail/SwitchExpression_03x.groovy b/src/test-resources/fail/SwitchExpression_03x.groovy
new file mode 100644
index 0000000..64ddad8
--- /dev/null
+++ b/src/test-resources/fail/SwitchExpression_03x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+switch(a) {
+    case 6 ->
+    case 8 ->
+    default -> 'b'
+}
diff --git a/src/test-resources/fail/SwitchExpression_04x.groovy b/src/test-resources/fail/SwitchExpression_04x.groovy
new file mode 100644
index 0000000..0315aa0
--- /dev/null
+++ b/src/test-resources/fail/SwitchExpression_04x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  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
+ *
+ *    http://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.
+ */
+
+def a = 6
+switch(a) {
+    case 6 -> 'a'
+    case 8 : 'b'
+    default -> 'c'
+}
diff --git a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
index f7f0e59..3cf890b 100644
--- a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
@@ -412,15 +412,31 @@ final class GroovyParserTest extends GroovyTestCase {
     }
 
     void "test groovy core - var"() {
-        doRunAndTestAntlr4('core/Var_01x.groovy');
+        doRunAndTestAntlr4('core/Var_01x.groovy')
     }
 
     void "test groovy core - String"() {
-        doRunAndTestAntlr4('core/String_01x.groovy');
+        doRunAndTestAntlr4('core/String_01x.groovy')
     }
 
     void "test groovy core - NonStaticClass"() {
-        doRunAndTestAntlr4('core/NonStaticClass_01x.groovy');
+        doRunAndTestAntlr4('core/NonStaticClass_01x.groovy')
+    }
+
+    void "test groovy core - SwitchExpression"() {
+        doRunAndTestAntlr4('core/SwitchExpression_01x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_02x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_03x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_04x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_05x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_06x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_07x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_08x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_09x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_10x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_11x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_12x.groovy')
+        doRunAndTestAntlr4('core/SwitchExpression_13x.groovy')
     }
 
     void "test groovy core - BUG"() {
@@ -445,30 +461,30 @@ final class GroovyParserTest extends GroovyTestCase {
     }
 
     void "test groovy core - GROOVY-9427"() {
-        doTest('bugs/BUG-GROOVY-9427.groovy');
+        doTest('bugs/BUG-GROOVY-9427.groovy')
     }
 
     void "test groovy core - GROOVY-9433"() {
-        doTest('bugs/BUG-GROOVY-9433.groovy');
+        doTest('bugs/BUG-GROOVY-9433.groovy')
     }
 
     void "test groovy core - GROOVY-9449"() {
-        doTest('bugs/BUG-GROOVY-9449.groovy');
+        doTest('bugs/BUG-GROOVY-9449.groovy')
     }
 
     void "test groovy core - GROOVY-9511"() {
-        doTest('bugs/BUG-GROOVY-9511.groovy', [MethodNode]);
+        doTest('bugs/BUG-GROOVY-9511.groovy', [MethodNode])
     }
 
     void "test groovy core - GROOVY-9507"() {
-        doTest('bugs/BUG-GROOVY-9507.groovy');
+        doTest('bugs/BUG-GROOVY-9507.groovy')
     }
 
     void "test groovy core - GROOVY-9522"() {
-        doTest('bugs/BUG-GROOVY-9522.groovy');
+        doTest('bugs/BUG-GROOVY-9522.groovy')
     }
 
     void "test groovy core - GROOVY-9692"() {
-        doTest('bugs/BUG-GROOVY-9692.groovy');
+        doTest('bugs/BUG-GROOVY-9692.groovy')
     }
 }
diff --git a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
index 4bbe12e..11ee7c3 100644
--- a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
@@ -224,7 +224,7 @@ final class SyntaxErrorTest extends GroovyTestCase {
     }
 
     void 'test groovy core - AnnotationDeclaration 1'() {
-        TestUtils.doRunAndShouldFail('fail/AnnotationDeclaration_01x.groovy');
+        TestUtils.doRunAndShouldFail('fail/AnnotationDeclaration_01x.groovy')
     }
 
     void 'test groovy core - AnnotationDeclaration 2'() {
@@ -403,6 +403,13 @@ final class SyntaxErrorTest extends GroovyTestCase {
         TestUtils.doRunAndShouldFail('fail/Array_02x.groovy')
     }
 
+    void "test groovy core - SwitchExpression"() {
+        TestUtils.doRunAndShouldFail('fail/SwitchExpression_01x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SwitchExpression_02x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SwitchExpression_03x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SwitchExpression_04x.groovy')
+    }
+
     @NotYetImplemented
     void 'test error alternative - Missing ")" 1'() {
         def err = expectParseError '''\
diff --git a/src/test/org/apache/groovy/parser/antlr4/TestUtils.groovy b/src/test/org/apache/groovy/parser/antlr4/TestUtils.groovy
index 3310447..c8aae4b 100644
--- a/src/test/org/apache/groovy/parser/antlr4/TestUtils.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/TestUtils.groovy
@@ -198,7 +198,7 @@ final class TestUtils {
     }
 
     static void doRunAndTestAntlr4(String path, CompilerConfiguration compilerConfiguration = CompilerConfiguration.DEFAULT) {
-        assert executeScript(createAntlr4Shell(compilerConfiguration), "$RESOURCES_PATH/$path")
+        assert executeScript(createAntlr4Shell(compilerConfiguration), path)
     }
 
     static void doRunAndTestAntlr2(String path, CompilerConfiguration compilerConfiguration = CompilerConfiguration.DEFAULT) {
@@ -255,7 +255,8 @@ final class TestUtils {
     }
 
     private static boolean executeScript(GroovyShell shell, String path) {
-        def file = new File(path)
+        File file = new File("$RESOURCES_PATH/$path")
+        assert file.exists() : "Test resource not found: $file.absolutePath"
         try {
             shell.evaluate(file.text)
             return true