http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
----------------------------------------------------------------------
diff --git 
a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
 
b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
new file mode 100644
index 0000000..0655c58
--- /dev/null
+++ 
b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -0,0 +1,4344 @@
+/*
+ *  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.
+ */
+package org.apache.groovy.parser.antlr4;
+
+import groovy.lang.IntRange;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.atn.PredictionMode;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.groovy.parser.antlr4.internal.AtnManager;
+import org.apache.groovy.parser.antlr4.internal.DescriptiveErrorStrategy;
+import org.apache.groovy.parser.antlr4.util.StringUtils;
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.antlr.EnumHelper;
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.ast.stmt.*;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+import org.codehaus.groovy.runtime.StringGroovyMethods;
+import org.codehaus.groovy.syntax.Numbers;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.*;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;
+
+/**
+ * Building the AST from the parse tree generated by Antlr4
+ *
+ * @author <a href="mailto:realblue...@hotmail.com";>Daniel.Sun</a>
+ *         Created on 2016/08/14
+ */
+public class AstBuilder extends GroovyParserBaseVisitor<Object> implements 
GroovyParserVisitor<Object> {
+
+    public AstBuilder(SourceUnit sourceUnit, ClassLoader classLoader) {
+        this.sourceUnit = sourceUnit;
+        this.moduleNode = new ModuleNode(sourceUnit);
+        this.classLoader = classLoader; // unused for the time being
+
+        this.lexer = new GroovyLangLexer(
+                new ANTLRInputStream(
+                        this.readSourceCode(sourceUnit)));
+        this.parser = new GroovyLangParser(
+                new CommonTokenStream(this.lexer));
+
+        this.parser.setErrorHandler(new DescriptiveErrorStrategy());
+
+        this.tryWithResourcesASTTransformation = new 
TryWithResourcesASTTransformation(this);
+    }
+
+    private GroovyParserRuleContext buildCST() {
+        GroovyParserRuleContext result;
+
+        // parsing have to wait util clearing is complete.
+        AtnManager.RRWL.readLock().lock();
+        try {
+            result = buildCST(PredictionMode.SLL);
+        } catch (Exception e) {
+            result = buildCST(PredictionMode.LL);
+        } finally {
+            AtnManager.RRWL.readLock().unlock();
+        }
+
+        return result;
+    }
+
+    private GroovyParserRuleContext buildCST(PredictionMode predictionMode) {
+        parser.getInterpreter().setPredictionMode(predictionMode);
+
+        if (PredictionMode.SLL.equals(predictionMode)) {
+            this.removeErrorListeners();
+        } else {
+            ((CommonTokenStream) parser.getInputStream()).reset();
+            this.addErrorListeners();
+        }
+
+        return parser.compilationUnit();
+    }
+
+    public ModuleNode buildAST() {
+        try {
+            return (ModuleNode) this.visit(this.buildCST());
+        } catch (Throwable t) {
+            CompilationFailedException cfe;
+
+            if (t instanceof CompilationFailedException) {
+                cfe = (CompilationFailedException) t;
+            } else if (t instanceof ParseCancellationException) {
+                cfe = createParsingFailedException(t.getCause());
+            } else {
+                cfe = createParsingFailedException(t);
+            }
+
+            LOGGER.log(Level.SEVERE, "Failed to build AST", cfe);
+
+            throw cfe;
+        }
+    }
+
+    @Override
+    public ModuleNode visitCompilationUnit(CompilationUnitContext ctx) {
+        this.visit(ctx.packageDeclaration());
+
+        ctx.statement().stream()
+                .map(this::visit)
+//                .filter(e -> e instanceof Statement)
+                .forEach(e -> {
+                    if (e instanceof DeclarationListStatement) { // local 
variable declaration
+                        ((DeclarationListStatement) 
e).getDeclarationStatements().forEach(moduleNode::addStatement);
+                    } else if (e instanceof Statement) {
+                        moduleNode.addStatement((Statement) e);
+                    } else if (e instanceof MethodNode) { // script method
+                        moduleNode.addMethod((MethodNode) e);
+                    }
+                });
+
+        this.classNodeList.forEach(moduleNode::addClass);
+
+        if (this.isPackageInfoDeclaration()) {
+            this.addPackageInfoClassNode();
+        } else {
+            // if groovy source file only contains blank(including EOF), add 
"return null" to the AST
+            if (this.isBlankScript(ctx)) {
+                this.addEmptyReturnStatement();
+            }
+        }
+
+        this.configureScriptClassNode();
+
+        return moduleNode;
+    }
+
+    @Override
+    public PackageNode visitPackageDeclaration(PackageDeclarationContext ctx) {
+        String packageName = this.visitQualifiedName(ctx.qualifiedName());
+        moduleNode.setPackageName(packageName + DOT_STR);
+
+        PackageNode packageNode = moduleNode.getPackage();
+
+        this.visitAnnotationsOpt(ctx.annotationsOpt()).stream()
+                .forEach(packageNode::addAnnotation);
+
+        return this.configureAST(packageNode, ctx);
+    }
+
+    @Override
+    public ImportNode visitImportDeclaration(ImportDeclarationContext ctx) {
+        // GROOVY-6094
+        moduleNode.putNodeMetaData(IMPORT_NODE_CLASS, IMPORT_NODE_CLASS);
+
+        ImportNode importNode;
+
+        boolean hasStatic = asBoolean(ctx.STATIC());
+        boolean hasStar = asBoolean(ctx.MUL());
+        boolean hasAlias = asBoolean(ctx.alias);
+
+        List<AnnotationNode> annotationNodeList = 
this.visitAnnotationsOpt(ctx.annotationsOpt());
+
+        if (hasStatic) {
+            if (hasStar) { // e.g. import static java.lang.Math.*
+                String qualifiedName = 
this.visitQualifiedName(ctx.qualifiedName());
+                ClassNode type = ClassHelper.make(qualifiedName);
+
+
+                moduleNode.addStaticStarImport(type.getText(), type, 
annotationNodeList);
+
+                importNode = last(moduleNode.getStaticStarImports().values());
+            } else { // e.g. import static java.lang.Math.pow
+                List<GroovyParserRuleContext> identifierList = new 
LinkedList<>(ctx.qualifiedName().qualifiedNameElement());
+                int identifierListSize = identifierList.size();
+                String name = identifierList.get(identifierListSize - 
1).getText();
+                ClassNode classNode =
+                        ClassHelper.make(
+                                identifierList.stream()
+                                        .limit(identifierListSize - 1)
+                                        .map(ParseTree::getText)
+                                        .collect(Collectors.joining(DOT_STR)));
+                String alias = hasAlias
+                        ? ctx.alias.getText()
+                        : name;
+
+                moduleNode.addStaticImport(classNode, name, alias, 
annotationNodeList);
+
+                importNode = last(moduleNode.getStaticImports().values());
+            }
+        } else {
+            if (hasStar) { // e.g. import java.util.*
+                String qualifiedName = 
this.visitQualifiedName(ctx.qualifiedName());
+
+                moduleNode.addStarImport(qualifiedName + DOT_STR, 
annotationNodeList);
+
+                importNode = last(moduleNode.getStarImports());
+            } else { // e.g. import java.util.Map
+                String qualifiedName = 
this.visitQualifiedName(ctx.qualifiedName());
+                String name = 
last(ctx.qualifiedName().qualifiedNameElement()).getText();
+                ClassNode classNode = ClassHelper.make(qualifiedName);
+                String alias = hasAlias
+                        ? ctx.alias.getText()
+                        : name;
+
+                moduleNode.addImport(alias, classNode, annotationNodeList);
+
+                importNode = last(moduleNode.getImports());
+            }
+        }
+
+        // TODO verify whether the following code is useful or not
+        // we're using node metadata here in order to fix GROOVY-6094
+        // without breaking external APIs
+        Object node = moduleNode.getNodeMetaData(IMPORT_NODE_CLASS);
+        if (null != node && IMPORT_NODE_CLASS != node) {
+            this.configureAST((ImportNode) node, importNode);
+        }
+        moduleNode.removeNodeMetaData(IMPORT_NODE_CLASS);
+
+        return this.configureAST(importNode, ctx);
+    }
+
+    // statement {    
--------------------------------------------------------------------
+    @Override
+    public AssertStatement visitAssertStmtAlt(AssertStmtAltContext ctx) {
+        Expression conditionExpression = (Expression) this.visit(ctx.ce);
+        BooleanExpression booleanExpression =
+                this.configureAST(
+                        new BooleanExpression(conditionExpression), 
conditionExpression);
+
+        if (!asBoolean(ctx.me)) {
+            return this.configureAST(
+                    new AssertStatement(booleanExpression), ctx);
+        }
+
+        return this.configureAST(new AssertStatement(booleanExpression,
+                        (Expression) this.visit(ctx.me)),
+                ctx);
+
+    }
+
+    @Override
+    public IfStatement visitIfElseStmtAlt(IfElseStmtAltContext ctx) {
+        Expression conditionExpression = 
this.visitParExpression(ctx.parExpression());
+        BooleanExpression booleanExpression =
+                this.configureAST(
+                        new BooleanExpression(conditionExpression), 
conditionExpression);
+
+        Statement ifBlock =
+                this.unpackStatement(
+                        (Statement) this.visit(ctx.tb));
+        Statement elseBlock =
+                this.unpackStatement(
+                        asBoolean(ctx.ELSE())
+                                ? (Statement) this.visit(ctx.fb)
+                                : EmptyStatement.INSTANCE);
+
+        return this.configureAST(new IfStatement(booleanExpression, ifBlock, 
elseBlock), ctx);
+    }
+
+    @Override
+    public Statement visitLoopStmtAlt(LoopStmtAltContext ctx) {
+        return this.configureAST((Statement) this.visit(ctx.loopStatement()), 
ctx);
+    }
+
+    @Override
+    public ForStatement visitForStmtAlt(ForStmtAltContext ctx) {
+        Pair<Parameter, Expression> controlPair = 
this.visitForControl(ctx.forControl());
+
+        Statement loopBlock = this.unpackStatement((Statement) 
this.visit(ctx.statement()));
+
+        return this.configureAST(
+                new ForStatement(controlPair.getKey(), controlPair.getValue(), 
asBoolean(loopBlock) ? loopBlock : EmptyStatement.INSTANCE),
+                ctx);
+    }
+
+    @Override
+    public Pair<Parameter, Expression> visitForControl(ForControlContext ctx) {
+        if (asBoolean(ctx.enhancedForControl())) { // e.g. for(int i in 
0..<10) {}
+            return this.visitEnhancedForControl(ctx.enhancedForControl());
+        }
+
+        if (asBoolean(ctx.SEMI())) { // e.g. for(int i = 0; i < 10; i++) {}
+            ClosureListExpression closureListExpression = new 
ClosureListExpression();
+
+            
closureListExpression.addExpression(this.visitForInit(ctx.forInit()));
+            closureListExpression.addExpression(asBoolean(ctx.expression()) ? 
(Expression) this.visit(ctx.expression()) : EmptyExpression.INSTANCE);
+            
closureListExpression.addExpression(this.visitForUpdate(ctx.forUpdate()));
+
+            return new Pair<>(ForStatement.FOR_LOOP_DUMMY, 
closureListExpression);
+        }
+
+        throw createParsingFailedException("Unsupported for control: " + 
ctx.getText(), ctx);
+    }
+
+    @Override
+    public Expression visitForInit(ForInitContext ctx) {
+        if (!asBoolean(ctx)) {
+            return EmptyExpression.INSTANCE;
+        }
+
+        if (asBoolean(ctx.localVariableDeclaration())) {
+            DeclarationListStatement declarationListStatement = 
this.visitLocalVariableDeclaration(ctx.localVariableDeclaration());
+
+            List<?> declarationExpressionList = 
declarationListStatement.getDeclarationExpressions();
+
+            if (declarationExpressionList.size() == 1) {
+                return this.configureAST((Expression) 
declarationExpressionList.get(0), ctx);
+            } else {
+                return this.configureAST(new 
ClosureListExpression((List<Expression>) declarationExpressionList), ctx);
+            }
+        }
+
+        if (asBoolean(ctx.expression())) {
+            return this.configureAST((Expression) 
this.visit(ctx.expression()), ctx);
+        }
+
+        throw createParsingFailedException("Unsupported for init: " + 
ctx.getText(), ctx);
+    }
+
+    @Override
+    public Expression visitForUpdate(ForUpdateContext ctx) {
+        if (!asBoolean(ctx)) {
+            return EmptyExpression.INSTANCE;
+        }
+
+        return this.configureAST((Expression) this.visit(ctx.expression()), 
ctx);
+    }
+
+
+    @Override
+    public Pair<Parameter, Expression> 
visitEnhancedForControl(EnhancedForControlContext ctx) {
+        Parameter parameter = this.configureAST(
+                new Parameter(this.visitType(ctx.type()), 
this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName()),
+                ctx.variableDeclaratorId());
+
+        // FIXME Groovy will ignore variableModifier of parameter in the for 
control
+        // In order to make the new parser behave same with the old one, we do 
not process variableModifier*
+
+        return new Pair<>(parameter, (Expression) 
this.visit(ctx.expression()));
+    }
+
+
+    @Override
+    public WhileStatement visitWhileStmtAlt(WhileStmtAltContext ctx) {
+        Expression conditionExpression = 
this.visitParExpression(ctx.parExpression());
+        BooleanExpression booleanExpression =
+                this.configureAST(
+                        new BooleanExpression(conditionExpression), 
conditionExpression);
+
+        Statement loopBlock = this.unpackStatement((Statement) 
this.visit(ctx.statement()));
+
+        return this.configureAST(
+                new WhileStatement(booleanExpression, asBoolean(loopBlock) ? 
loopBlock : EmptyStatement.INSTANCE),
+                ctx);
+    }
+
+    @Override
+    public DoWhileStatement visitDoWhileStmtAlt(DoWhileStmtAltContext ctx) {
+        Expression conditionExpression = 
this.visitParExpression(ctx.parExpression());
+
+        BooleanExpression booleanExpression =
+                this.configureAST(
+                        new BooleanExpression(conditionExpression),
+                        conditionExpression
+                );
+
+        Statement loopBlock = this.unpackStatement((Statement) 
this.visit(ctx.statement()));
+
+        return this.configureAST(
+                new DoWhileStatement(booleanExpression, asBoolean(loopBlock) ? 
loopBlock : EmptyStatement.INSTANCE),
+                ctx);
+    }
+
+    @Override
+    public Statement visitTryCatchStmtAlt(TryCatchStmtAltContext ctx) {
+        return 
this.configureAST(this.visitTryCatchStatement(ctx.tryCatchStatement()), ctx);
+    }
+
+    @Override
+    public Statement visitTryCatchStatement(TryCatchStatementContext ctx) {
+        TryCatchStatement tryCatchStatement =
+                new TryCatchStatement((Statement) this.visit(ctx.block()),
+                        this.visitFinallyBlock(ctx.finallyBlock()));
+
+        if (asBoolean(ctx.resources())) {
+            this.visitResources(ctx.resources()).stream()
+                    .forEach(tryCatchStatement::addResource);
+        }
+
+        ctx.catchClause().stream().map(this::visitCatchClause)
+                .reduce(new LinkedList<CatchStatement>(), (r, e) -> {
+                    r.addAll(e); // merge several LinkedList<CatchStatement> 
instances into one LinkedList<CatchStatement> instance
+                    return r;
+                })
+                .forEach(tryCatchStatement::addCatch);
+
+        return this.configureAST(
+                tryWithResourcesASTTransformation.transform(
+                        this.configureAST(tryCatchStatement, ctx)),
+                ctx);
+    }
+
+
+    @Override
+    public List<ExpressionStatement> visitResources(ResourcesContext ctx) {
+        return this.visitResourceList(ctx.resourceList());
+    }
+
+    @Override
+    public List<ExpressionStatement> visitResourceList(ResourceListContext 
ctx) {
+        return 
ctx.resource().stream().map(this::visitResource).collect(Collectors.toList());
+    }
+
+    @Override
+    public ExpressionStatement visitResource(ResourceContext ctx) {
+        List<ExpressionStatement> declarationStatements = 
this.visitLocalVariableDeclaration(ctx.localVariableDeclaration()).getDeclarationStatements();
+
+        if (declarationStatements.size() > 1) {
+            throw createParsingFailedException("Multi resources can not be 
declared in one statement", ctx);
+        }
+
+        return declarationStatements.get(0);
+    }
+
+    /**
+     * Multi-catch(1..*) clause will be unpacked to several normal catch 
clauses, so the return type is List
+     *
+     * @param ctx the parse tree
+     * @return
+     */
+    @Override
+    public List<CatchStatement> visitCatchClause(CatchClauseContext ctx) {
+        // FIXME Groovy will ignore variableModifier of parameter in the catch 
clause
+        // In order to make the new parser behave same with the old one, we do 
not process variableModifier*
+
+        return this.visitCatchType(ctx.catchType()).stream()
+                .map(e -> this.configureAST(
+                        new CatchStatement(
+                                // FIXME The old parser does not set location 
info for the parameter of the catch clause.
+                                // we could make it better
+                                //this.configureAST(new Parameter(e, 
this.visitIdentifier(ctx.identifier())), ctx.Identifier()),
+
+                                new Parameter(e, 
this.visitIdentifier(ctx.identifier())),
+                                this.visitBlock(ctx.block())),
+                        ctx.block()))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ClassNode> visitCatchType(CatchTypeContext ctx) {
+        if (!asBoolean(ctx)) {
+            return Collections.singletonList(ClassHelper.OBJECT_TYPE);
+        }
+
+        return ctx.qualifiedClassName().stream()
+                .map(this::visitQualifiedClassName)
+                .collect(Collectors.toList());
+    }
+
+
+    @Override
+    public Statement visitFinallyBlock(FinallyBlockContext ctx) {
+        if (!asBoolean(ctx)) {
+            return EmptyStatement.INSTANCE;
+        }
+
+        return this.configureAST(
+                this.createBlockStatement((Statement) this.visit(ctx.block())),
+                ctx);
+    }
+
+    @Override
+    public SwitchStatement visitSwitchStmtAlt(SwitchStmtAltContext ctx) {
+        return 
this.configureAST(this.visitSwitchStatement(ctx.switchStatement()), ctx);
+    }
+
+    public SwitchStatement visitSwitchStatement(SwitchStatementContext ctx) {
+        List<Statement> statementList =
+                ctx.switchBlockStatementGroup().stream()
+                        .map(this::visitSwitchBlockStatementGroup)
+                        .reduce(new LinkedList<>(), (r, e) -> {
+                            r.addAll(e);
+                            return r;
+                        });
+
+        List<CaseStatement> caseStatementList = new LinkedList<>();
+        List<Statement> defaultStatementList = new LinkedList<>();
+
+        statementList.stream().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 statement 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));
+        }
+
+        return this.configureAST(
+                new SwitchStatement(
+                        this.visitParExpression(ctx.parExpression()),
+                        caseStatementList,
+                        defaultStatementListSize == 0 ? 
EmptyStatement.INSTANCE : defaultStatementList.get(0)
+                ),
+                ctx);
+
+    }
+
+
+    @Override
+    @SuppressWarnings({"unchecked"})
+    public List<Statement> 
visitSwitchBlockStatementGroup(SwitchBlockStatementGroupContext ctx) {
+        int labelCnt = ctx.switchLabel().size();
+        List<Token> firstLabelHolder = new ArrayList<>(1);
+
+        return (List<Statement>) ctx.switchLabel().stream()
+                .map(e -> (Object) this.visitSwitchLabel(e))
+                .reduce(new ArrayList<Statement>(4), (r, e) -> {
+                    List<Statement> statementList = (List<Statement>) r;
+                    Pair<Token, Expression> pair = (Pair<Token, Expression>) e;
+
+                    boolean isLast = labelCnt - 1 == statementList.size();
+
+                    switch (pair.getKey().getType()) {
+                        case CASE: {
+                            if (!asBoolean(statementList)) {
+                                firstLabelHolder.add(pair.getKey());
+                            }
+
+                            statementList.add(
+                                    this.configureAST(
+                                            new CaseStatement(
+                                                    pair.getValue(),
+
+                                                    // check whether 
processing the last label. if yes, block statement should be attached.
+                                                    isLast ? 
this.visitBlockStatements(ctx.blockStatements())
+                                                            : 
EmptyStatement.INSTANCE
+                                            ),
+                                            firstLabelHolder.get(0)));
+
+                            break;
+                        }
+                        case DEFAULT: {
+
+                            BlockStatement blockStatement = 
this.visitBlockStatements(ctx.blockStatements());
+                            blockStatement.putNodeMetaData(IS_SWITCH_DEFAULT, 
true);
+
+                            statementList.add(
+                                    // this.configureAST(blockStatement, 
pair.getKey())
+                                    blockStatement
+                            );
+
+                            break;
+                        }
+                    }
+
+                    return statementList;
+                });
+
+    }
+
+    @Override
+    public Pair<Token, Expression> visitSwitchLabel(SwitchLabelContext ctx) {
+        if (asBoolean(ctx.CASE())) {
+            return new Pair<>(ctx.CASE().getSymbol(), (Expression) 
this.visit(ctx.expression()));
+        } else if (asBoolean(ctx.DEFAULT())) {
+            return new Pair<>(ctx.DEFAULT().getSymbol(), 
EmptyExpression.INSTANCE);
+        }
+
+        throw createParsingFailedException("Unsupported switch label: " + 
ctx.getText(), ctx);
+    }
+
+
+    @Override
+    public SynchronizedStatement 
visitSynchronizedStmtAlt(SynchronizedStmtAltContext ctx) {
+        return this.configureAST(
+                new 
SynchronizedStatement(this.visitParExpression(ctx.parExpression()), 
this.visitBlock(ctx.block())),
+                ctx);
+    }
+
+
+    @Override
+    public ExpressionStatement visitExpressionStmtAlt(ExpressionStmtAltContext 
ctx) {
+        return (ExpressionStatement) this.visit(ctx.statementExpression());
+    }
+
+    @Override
+    public ReturnStatement visitReturnStmtAlt(ReturnStmtAltContext ctx) {
+        return this.configureAST(new 
ReturnStatement(asBoolean(ctx.expression())
+                        ? (Expression) this.visit(ctx.expression())
+                        : ConstantExpression.EMPTY_EXPRESSION),
+                ctx);
+    }
+
+    @Override
+    public ThrowStatement visitThrowStmtAlt(ThrowStmtAltContext ctx) {
+        return this.configureAST(
+                new ThrowStatement((Expression) this.visit(ctx.expression())),
+                ctx);
+    }
+
+    @Override
+    public Statement visitLabeledStmtAlt(LabeledStmtAltContext ctx) {
+        Statement statement = (Statement) this.visit(ctx.statement());
+
+        statement.addStatementLabel(this.visitIdentifier(ctx.identifier()));
+
+        return statement; // this.configureAST(statement, ctx);
+    }
+
+    @Override
+    public BreakStatement visitBreakStatement(BreakStatementContext ctx) {
+        String label = asBoolean(ctx.identifier())
+                ? this.visitIdentifier(ctx.identifier())
+                : null;
+
+        return this.configureAST(new BreakStatement(label), ctx);
+    }
+
+    @Override
+    public BreakStatement visitBreakStmtAlt(BreakStmtAltContext ctx) {
+        return 
this.configureAST(this.visitBreakStatement(ctx.breakStatement()), ctx);
+    }
+
+    @Override
+    public ContinueStatement visitContinueStatement(ContinueStatementContext 
ctx) {
+        String label = asBoolean(ctx.identifier())
+                ? this.visitIdentifier(ctx.identifier())
+                : null;
+
+        return this.configureAST(new ContinueStatement(label), ctx);
+
+    }
+
+    @Override
+    public ContinueStatement visitContinueStmtAlt(ContinueStmtAltContext ctx) {
+        return 
this.configureAST(this.visitContinueStatement(ctx.continueStatement()), ctx);
+    }
+
+    @Override
+    public ImportNode visitImportStmtAlt(ImportStmtAltContext ctx) {
+        return 
this.configureAST(this.visitImportDeclaration(ctx.importDeclaration()), ctx);
+    }
+
+    @Override
+    public ClassNode visitTypeDeclarationStmtAlt(TypeDeclarationStmtAltContext 
ctx) {
+        return 
this.configureAST(this.visitTypeDeclaration(ctx.typeDeclaration()), ctx);
+    }
+
+
+    @Override
+    public Statement 
visitLocalVariableDeclarationStmtAlt(LocalVariableDeclarationStmtAltContext 
ctx) {
+        return 
this.configureAST(this.visitLocalVariableDeclaration(ctx.localVariableDeclaration()),
 ctx);
+    }
+
+    @Override
+    public MethodNode 
visitMethodDeclarationStmtAlt(MethodDeclarationStmtAltContext ctx) {
+        return 
this.configureAST(this.visitMethodDeclaration(ctx.methodDeclaration()), ctx);
+    }
+
+    // } statement    
--------------------------------------------------------------------
+
+    @Override
+    public ClassNode visitTypeDeclaration(TypeDeclarationContext ctx) {
+        if (asBoolean(ctx.classDeclaration())) { // e.g. class A {}
+            ctx.classDeclaration().putNodeMetaData(TYPE_DECLARATION_MODIFIERS, 
this.visitClassOrInterfaceModifiersOpt(ctx.classOrInterfaceModifiersOpt()));
+            return 
this.configureAST(this.visitClassDeclaration(ctx.classDeclaration()), ctx);
+        }
+
+        throw createParsingFailedException("Unsupported type declaration: " + 
ctx.getText(), ctx);
+    }
+
+    private void initUsingGenerics(ClassNode classNode) {
+        if (classNode.isUsingGenerics()) {
+            return;
+        }
+
+        if (!classNode.isEnum()) {
+            
classNode.setUsingGenerics(classNode.getSuperClass().isUsingGenerics());
+        }
+
+        if (!classNode.isUsingGenerics() && asBoolean((Object) 
classNode.getInterfaces())) {
+            for (ClassNode anInterface : classNode.getInterfaces()) {
+                classNode.setUsingGenerics(classNode.isUsingGenerics() || 
anInterface.isUsingGenerics());
+
+                if (classNode.isUsingGenerics())
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public ClassNode visitClassDeclaration(ClassDeclarationContext ctx) {
+        String packageName = moduleNode.getPackageName();
+        packageName = asBoolean((Object) packageName) ? packageName : "";
+
+        List<ModifierNode> modifierNodeList = 
ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS);
+        Objects.requireNonNull(modifierNodeList, "modifierNodeList should not 
be null");
+
+        ModifierManager modifierManager = new 
ModifierManager(modifierNodeList);
+        int modifiers = modifierManager.getClassModifiersOpValue();
+
+        boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0);
+        modifiers &= ~Opcodes.ACC_SYNTHETIC;
+
+        final ClassNode outerClass = classNodeStack.peek();
+        ClassNode classNode;
+        String className = this.visitIdentifier(ctx.identifier());
+        if (asBoolean(ctx.ENUM())) {
+            classNode =
+                    EnumHelper.makeEnumNode(
+                            asBoolean(outerClass) ? className : packageName + 
className,
+                            modifiers, null, outerClass);
+        } else {
+            if (asBoolean(outerClass)) {
+                classNode =
+                        new InnerClassNode(
+                                outerClass,
+                                outerClass.getName() + "$" + className,
+                                modifiers | (outerClass.isInterface() ? 
Opcodes.ACC_STATIC : 0),
+                                ClassHelper.OBJECT_TYPE);
+            } else {
+                classNode =
+                        new ClassNode(
+                                packageName + className,
+                                modifiers,
+                                ClassHelper.OBJECT_TYPE);
+            }
+
+        }
+
+        this.configureAST(classNode, ctx);
+        classNode.putNodeMetaData(CLASS_NAME, className);
+        classNode.setSyntheticPublic(syntheticPublic);
+
+        if (asBoolean(ctx.TRAIT())) {
+            classNode.addAnnotation(new 
AnnotationNode(ClassHelper.make(GROOVY_TRANSFORM_TRAIT)));
+        }
+        classNode.addAnnotations(modifierManager.getAnnotations());
+        
classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
+
+        if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT())) { // class OR 
trait
+            classNode.setSuperClass(this.visitType(ctx.sc));
+            classNode.setInterfaces(this.visitTypeList(ctx.is));
+
+            this.initUsingGenerics(classNode);
+        } else if (asBoolean(ctx.INTERFACE()) && !asBoolean(ctx.AT())) { // 
interface(NOT annotation)
+            classNode.setModifiers(classNode.getModifiers() | 
Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT);
+
+            classNode.setSuperClass(ClassHelper.OBJECT_TYPE);
+            classNode.setInterfaces(this.visitTypeList(ctx.scs));
+
+            this.initUsingGenerics(classNode);
+
+            this.hackMixins(classNode);
+        } else if (asBoolean(ctx.ENUM())) { // enum
+            classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_ENUM 
| Opcodes.ACC_FINAL);
+
+            classNode.setInterfaces(this.visitTypeList(ctx.is));
+
+            this.initUsingGenerics(classNode);
+        } else if (asBoolean(ctx.AT())) { // annotation
+            classNode.setModifiers(classNode.getModifiers() | 
Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_ANNOTATION);
+
+            classNode.addInterface(ClassHelper.Annotation_TYPE);
+
+            this.hackMixins(classNode);
+        } else {
+            throw createParsingFailedException("Unsupported class declaration: 
" + ctx.getText(), ctx);
+        }
+
+        // we put the class already in output to avoid the most inner classes
+        // will be used as first class later in the loader. The first class
+        // there determines what GCL#parseClass for example will return, so we
+        // have here to ensure it won't be the inner class
+        if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT())) {
+            classNodeList.add(classNode);
+        }
+
+        int oldAnonymousInnerClassCounter = this.anonymousInnerClassCounter;
+        classNodeStack.push(classNode);
+        ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, 
classNode);
+        this.visitClassBody(ctx.classBody());
+        classNodeStack.pop();
+        this.anonymousInnerClassCounter = oldAnonymousInnerClassCounter;
+
+        if (!(asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT()))) {
+            classNodeList.add(classNode);
+        }
+
+        this.attachDocCommentAsMetaData(classNode, ctx);
+
+        return classNode;
+    }
+
+    @Override
+    public Void visitClassBody(ClassBodyContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        if (asBoolean(ctx.enumConstants())) {
+            ctx.enumConstants().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, 
classNode);
+            this.visitEnumConstants(ctx.enumConstants());
+        }
+
+        ctx.classBodyDeclaration().stream().forEach(e -> {
+            e.putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+            this.visitClassBodyDeclaration(e);
+        });
+
+        return null;
+    }
+
+    @Override
+    public List<FieldNode> visitEnumConstants(EnumConstantsContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        return ctx.enumConstant().stream()
+                .map(e -> {
+                    e.putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+                    return this.visitEnumConstant(e);
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public FieldNode visitEnumConstant(EnumConstantContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        InnerClassNode anonymousInnerClassNode = null;
+        if (asBoolean(ctx.anonymousInnerClassDeclaration())) {
+            
ctx.anonymousInnerClassDeclaration().putNodeMetaData(ANONYMOUS_INNER_CLASS_SUPER_CLASS,
 classNode);
+            anonymousInnerClassNode = 
this.visitAnonymousInnerClassDeclaration(ctx.anonymousInnerClassDeclaration());
+        }
+
+        FieldNode enumConstant =
+                EnumHelper.addEnumConstant(
+                        classNode,
+                        this.visitIdentifier(ctx.identifier()),
+                        createEnumConstantInitExpression(ctx.arguments(), 
anonymousInnerClassNode));
+
+        
this.visitAnnotationsOpt(ctx.annotationsOpt()).forEach(enumConstant::addAnnotation);
+
+        this.attachDocCommentAsMetaData(enumConstant, ctx);
+
+        return this.configureAST(enumConstant, ctx);
+    }
+
+    private Expression createEnumConstantInitExpression(ArgumentsContext ctx, 
InnerClassNode anonymousInnerClassNode) {
+        if (!asBoolean(ctx) && !asBoolean(anonymousInnerClassNode)) {
+            return null;
+        }
+
+        TupleExpression argumentListExpression = (TupleExpression) 
this.visitArguments(ctx);
+        List<Expression> expressions = argumentListExpression.getExpressions();
+
+        if (expressions.size() == 1) {
+            Expression expression = expressions.get(0);
+
+            if (expression instanceof NamedArgumentListExpression) { // e.g. 
SOME_ENUM_CONSTANT(a: "1", b: "2")
+                List<MapEntryExpression> mapEntryExpressionList = 
((NamedArgumentListExpression) expression).getMapEntryExpressions();
+                ListExpression listExpression =
+                        new ListExpression(
+                                mapEntryExpressionList.stream()
+                                        .map(e -> (Expression) e)
+                                        .collect(Collectors.toList()));
+
+                if (asBoolean(anonymousInnerClassNode)) {
+                    listExpression.addExpression(
+                            this.configureAST(
+                                    new 
ClassExpression(anonymousInnerClassNode),
+                                    anonymousInnerClassNode));
+                }
+
+                if (mapEntryExpressionList.size() > 1) {
+                    listExpression.setWrapped(true);
+                }
+
+                return this.configureAST(listExpression, ctx);
+            }
+
+            if (!asBoolean(anonymousInnerClassNode)) {
+                return expression;
+            }
+
+            ListExpression listExpression = new ListExpression();
+            listExpression.addExpression(expression);
+            listExpression.addExpression(
+                    this.configureAST(
+                            new ClassExpression(anonymousInnerClassNode),
+                            anonymousInnerClassNode));
+
+            return this.configureAST(listExpression, ctx);
+        }
+
+        ListExpression listExpression = new ListExpression(expressions);
+        if (asBoolean(anonymousInnerClassNode)) {
+            listExpression.addExpression(
+                    this.configureAST(
+                            new ClassExpression(anonymousInnerClassNode),
+                            anonymousInnerClassNode));
+        }
+
+        if (asBoolean(ctx)) {
+            listExpression.setWrapped(true);
+        }
+
+        return asBoolean(ctx)
+                ? this.configureAST(listExpression, ctx)
+                : this.configureAST(listExpression, anonymousInnerClassNode);
+    }
+
+
+    @Override
+    public Void visitClassBodyDeclaration(ClassBodyDeclarationContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        if (asBoolean(ctx.memberDeclaration())) {
+            
ctx.memberDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, 
classNode);
+            this.visitMemberDeclaration(ctx.memberDeclaration());
+        } else if (asBoolean(ctx.block())) {
+            Statement statement = this.visitBlock(ctx.block());
+
+            if (asBoolean(ctx.STATIC())) { // e.g. static { }
+                
classNode.addStaticInitializerStatements(Collections.singletonList(statement), 
false);
+            } else { // e.g.  { }
+                classNode.addObjectInitializerStatements(
+                        this.configureAST(
+                                this.createBlockStatement(statement),
+                                statement));
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public Void visitMemberDeclaration(MemberDeclarationContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        if (asBoolean(ctx.methodDeclaration())) {
+            
ctx.methodDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, 
classNode);
+            this.visitMethodDeclaration(ctx.methodDeclaration());
+        } else if (asBoolean(ctx.fieldDeclaration())) {
+            
ctx.fieldDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+            this.visitFieldDeclaration(ctx.fieldDeclaration());
+        } else if (asBoolean(ctx.classDeclaration())) {
+            ctx.classDeclaration().putNodeMetaData(TYPE_DECLARATION_MODIFIERS, 
this.visitModifiersOpt(ctx.modifiersOpt()));
+            
ctx.classDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+            this.visitClassDeclaration(ctx.classDeclaration());
+        }
+
+        return null;
+    }
+
+    @Override
+    public GenericsType[] visitTypeParameters(TypeParametersContext ctx) {
+        if (!asBoolean(ctx)) {
+            return null;
+        }
+
+        return ctx.typeParameter().stream()
+                .map(this::visitTypeParameter)
+                .toArray(GenericsType[]::new);
+    }
+
+    @Override
+    public GenericsType visitTypeParameter(TypeParameterContext ctx) {
+        return this.configureAST(
+                new GenericsType(
+                        ClassHelper.make(this.visitClassName(ctx.className())),
+                        this.visitTypeBound(ctx.typeBound()),
+                        null
+                ),
+                ctx);
+    }
+
+    @Override
+    public ClassNode[] visitTypeBound(TypeBoundContext ctx) {
+        if (!asBoolean(ctx)) {
+            return null;
+        }
+
+        return ctx.type().stream()
+                .map(this::visitType)
+                .toArray(ClassNode[]::new);
+    }
+
+    @Override
+    public Void visitFieldDeclaration(FieldDeclarationContext ctx) {
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        Objects.requireNonNull(classNode, "classNode should not be null");
+
+        
ctx.variableDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, 
classNode);
+        this.visitVariableDeclaration(ctx.variableDeclaration());
+
+        return null;
+    }
+
+    private ConstructorCallExpression 
checkThisAndSuperConstructorCall(Statement statement) {
+        if (!(statement instanceof BlockStatement)) { // method code must be a 
BlockStatement
+            return null;
+        }
+
+        BlockStatement blockStatement = (BlockStatement) statement;
+        List<Statement> statementList = blockStatement.getStatements();
+
+        for (int i = 0, n = statementList.size(); i < n; i++) {
+            Statement s = statementList.get(i);
+            if (s instanceof ExpressionStatement) {
+                Expression expression = ((ExpressionStatement) 
s).getExpression();
+                if ((expression instanceof ConstructorCallExpression) && 0 != 
i) {
+                    return (ConstructorCallExpression) expression;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public MethodNode visitMethodDeclaration(MethodDeclarationContext ctx) {
+        List<ModifierNode> modifierNodeList = Collections.emptyList();
+
+        if (asBoolean(ctx.modifiers())) {
+            modifierNodeList = this.visitModifiers(ctx.modifiers());
+        } else if (asBoolean(ctx.modifiersOpt())) {
+            modifierNodeList = this.visitModifiersOpt(ctx.modifiersOpt());
+        }
+
+        ModifierManager modifierManager = new 
ModifierManager(modifierNodeList);
+        String methodName = this.visitMethodName(ctx.methodName());
+        ClassNode returnType = this.visitReturnType(ctx.returnType());
+        Parameter[] parameters = 
this.visitFormalParameters(ctx.formalParameters());
+        ClassNode[] exceptions = 
this.visitQualifiedClassNameList(ctx.qualifiedClassNameList());
+
+        anonymousInnerClassesDefinedInMethodStack.push(new LinkedList<>());
+        Statement code = this.visitMethodBody(ctx.methodBody());
+        List<InnerClassNode> anonymousInnerClassList = 
anonymousInnerClassesDefinedInMethodStack.pop();
+
+        MethodNode methodNode;
+        // if classNode is not null, the method declaration is for class 
declaration
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+        if (asBoolean(classNode)) {
+            String className = classNode.getNodeMetaData(CLASS_NAME);
+            int modifiers = modifierManager.getClassMemberModifiersOpValue();
+
+            if (!asBoolean(ctx.returnType())
+                    && asBoolean(ctx.methodBody())
+                    && methodName.equals(className)) { // constructor 
declaration
+
+                ConstructorCallExpression thisOrSuperConstructorCallExpression 
= this.checkThisAndSuperConstructorCall(code);
+                if (asBoolean(thisOrSuperConstructorCallExpression)) {
+                    throw 
createParsingFailedException(thisOrSuperConstructorCallExpression.getText() + " 
should be the first statement in the constructor[" + methodName + "]", 
thisOrSuperConstructorCallExpression);
+                }
+
+                methodNode =
+                        classNode.addConstructor(
+                                modifiers,
+                                parameters,
+                                exceptions,
+                                code);
+
+            } else { // class memeber method declaration
+                if (asBoolean(ctx.elementValue())) { // the code of annotation 
method
+                    code = this.configureAST(
+                            new ExpressionStatement(
+                                    
this.visitElementValue(ctx.elementValue())),
+                            ctx.elementValue());
+
+                }
+
+                modifiers |= classNode.isInterface() ? Opcodes.ACC_ABSTRACT : 
0;
+                methodNode = classNode.addMethod(methodName, modifiers, 
returnType, parameters, exceptions, code);
+
+                methodNode.setAnnotationDefault(asBoolean(ctx.elementValue()));
+            }
+
+            modifierManager.attachAnnotations(methodNode);
+
+            this.attachDocCommentAsMetaData(methodNode, ctx);
+        } else { // script method declaration
+            methodNode =
+                    new MethodNode(
+                            methodName,
+                            modifierManager.contains(PRIVATE) ? 
Opcodes.ACC_PRIVATE : Opcodes.ACC_PUBLIC,
+                            returnType,
+                            parameters,
+                            exceptions,
+                            code);
+
+            modifierManager.processMethodNode(methodNode);
+        }
+        anonymousInnerClassList.stream().forEach(e -> 
e.setEnclosingMethod(methodNode));
+
+        
methodNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
+        methodNode.setSyntheticPublic(
+                this.isSyntheticPublic(
+                        this.isAnnotationDeclaration(classNode),
+                        classNode instanceof EnumConstantClassNode,
+                        asBoolean(ctx.returnType()),
+                        modifierManager));
+
+        if (modifierManager.contains(STATIC)) {
+            Arrays.stream(methodNode.getParameters()).forEach(e -> 
e.setInStaticContext(true));
+            methodNode.getVariableScope().setInStaticContext(true);
+        }
+
+        this.configureAST(methodNode, ctx);
+
+        boolean isAbstractMethod = methodNode.isAbstract();
+        boolean hasMethodBody = asBoolean(methodNode.getCode());
+
+        if (9 == ctx.ct) { // script
+            if (isAbstractMethod || !hasMethodBody) { // method should not be 
declared abstract in the script
+                throw createParsingFailedException("You can not define a " + 
(isAbstractMethod ? "abstract" : "") + " method[" + methodNode.getName() + "] " 
+ (!hasMethodBody ? "without method body" : "") + " in the script. Try " + 
(isAbstractMethod ? "removing the 'abstract'" : "") + (isAbstractMethod && 
!hasMethodBody ? " and" : "") + (!hasMethodBody ? " adding a method body" : 
""), methodNode);
+            }
+        } else {
+            if (!isAbstractMethod && !hasMethodBody) { // non-abstract method 
without body in the non-script(e.g. class, enum, trait) is not allowed!
+                throw createParsingFailedException("You defined a method[" + 
methodNode.getName() + "] without body. Try adding a method body, or declare it 
abstract", methodNode);
+            }
+        }
+
+        return methodNode;
+    }
+
+    @Override
+    public String visitMethodName(MethodNameContext ctx) {
+        if (asBoolean(ctx.identifier())) {
+            return this.visitIdentifier(ctx.identifier());
+        }
+
+        if (asBoolean(ctx.stringLiteral())) {
+            return this.visitStringLiteral(ctx.stringLiteral()).getText();
+        }
+
+        throw createParsingFailedException("Unsupported method name: " + 
ctx.getText(), ctx);
+    }
+
+    @Override
+    public ClassNode visitReturnType(ReturnTypeContext ctx) {
+        if (!asBoolean(ctx)) {
+            return ClassHelper.OBJECT_TYPE;
+        }
+
+        if (asBoolean(ctx.type())) {
+            return this.visitType(ctx.type());
+        }
+
+        if (asBoolean(ctx.VOID())) {
+            return ClassHelper.VOID_TYPE;
+        }
+
+        throw createParsingFailedException("Unsupported return type: " + 
ctx.getText(), ctx);
+    }
+
+    @Override
+    public Statement visitMethodBody(MethodBodyContext ctx) {
+        if (!asBoolean(ctx)) {
+            return null;
+        }
+
+        return this.configureAST(this.visitBlock(ctx.block()), ctx);
+    }
+
+    @Override
+    public DeclarationListStatement 
visitLocalVariableDeclaration(LocalVariableDeclarationContext ctx) {
+        return 
this.configureAST(this.visitVariableDeclaration(ctx.variableDeclaration()), 
ctx);
+    }
+
+    @Override
+    public DeclarationListStatement 
visitVariableDeclaration(VariableDeclarationContext ctx) {
+        List<ModifierNode> modifierNodeList = Collections.emptyList();
+
+        if (asBoolean(ctx.variableModifiers())) {
+            modifierNodeList = 
this.visitVariableModifiers(ctx.variableModifiers());
+        } else if (asBoolean(ctx.variableModifiersOpt())) {
+            modifierNodeList = 
this.visitVariableModifiersOpt(ctx.variableModifiersOpt());
+        } else if (asBoolean(ctx.modifiers())) {
+            modifierNodeList = this.visitModifiers(ctx.modifiers());
+        } else if (asBoolean(ctx.modifiersOpt())) {
+            modifierNodeList = this.visitModifiersOpt(ctx.modifiersOpt());
+        }
+
+        ModifierManager modifierManager = new 
ModifierManager(modifierNodeList);
+
+        if (asBoolean(ctx.typeNamePairs())) { // e.g. def (int a, int b) = [1, 
2]
+            if (!modifierManager.contains(DEF)) {
+                throw createParsingFailedException("keyword def is required to 
declare tuple, e.g. def (int a, int b) = [1, 2]", ctx);
+            }
+
+            return this.configureAST(
+                    new DeclarationListStatement(
+                            this.configureAST(
+                                    modifierManager.attachAnnotations(
+                                            new DeclarationExpression(
+                                                    new ArgumentListExpression(
+                                                            
this.visitTypeNamePairs(ctx.typeNamePairs()).stream()
+                                                                    .peek(e -> 
modifierManager.processVariableExpression((VariableExpression) e))
+                                                                    
.collect(Collectors.toList())
+                                                    ),
+                                                    
this.createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN),
+                                                    
this.visitVariableInitializer(ctx.variableInitializer())
+                                            )
+                                    ),
+                                    ctx
+                            )
+                    ),
+                    ctx
+            );
+        }
+
+        ClassNode variableType = this.visitType(ctx.type());
+        
ctx.variableDeclarators().putNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE, 
variableType);
+        List<DeclarationExpression> declarationExpressionList = 
this.visitVariableDeclarators(ctx.variableDeclarators());
+
+        // if classNode is not null, the variable declaration is for class 
declaration. In other words, it is a field declaration
+        ClassNode classNode = 
ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+
+        if (asBoolean(classNode)) {
+            declarationExpressionList.stream().forEach(e -> {
+                VariableExpression variableExpression = (VariableExpression) 
e.getLeftExpression();
+
+                int modifiers = 
modifierManager.getClassMemberModifiersOpValue();
+
+                Expression initialValue = 
EmptyExpression.INSTANCE.equals(e.getRightExpression()) ? null : 
e.getRightExpression();
+                Object defaultValue = findDefaultValueByType(variableType);
+
+                if (classNode.isInterface()) {
+                    if (!asBoolean(initialValue)) {
+                        initialValue = !asBoolean(defaultValue) ? null : new 
ConstantExpression(defaultValue);
+                    }
+
+                    modifiers |= Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | 
Opcodes.ACC_FINAL;
+                }
+
+                if (classNode.isInterface() || 
modifierManager.containsVisibilityModifier()) {
+                    FieldNode fieldNode =
+                            classNode.addField(
+                                    variableExpression.getName(),
+                                    modifiers,
+                                    variableType,
+                                    initialValue);
+                    modifierManager.attachAnnotations(fieldNode);
+
+                    this.attachDocCommentAsMetaData(fieldNode, ctx);
+
+                    this.configureAST(fieldNode, ctx);
+                } else {
+                    PropertyNode propertyNode =
+                            classNode.addProperty(
+                                    variableExpression.getName(),
+                                    modifiers | Opcodes.ACC_PUBLIC,
+                                    variableType,
+                                    initialValue,
+                                    null,
+                                    null);
+
+                    FieldNode fieldNode = propertyNode.getField();
+                    fieldNode.setModifiers(modifiers & ~Opcodes.ACC_PUBLIC | 
Opcodes.ACC_PRIVATE);
+                    fieldNode.setSynthetic(!classNode.isInterface());
+                    modifierManager.attachAnnotations(fieldNode);
+
+                    this.attachDocCommentAsMetaData(fieldNode, ctx);
+                    this.attachDocCommentAsMetaData(propertyNode, ctx);
+
+                    this.configureAST(fieldNode, ctx);
+                    this.configureAST(propertyNode, ctx);
+                }
+
+            });
+
+
+            return null;
+        }
+
+        declarationExpressionList.stream().forEach(e -> {
+            VariableExpression variableExpression = (VariableExpression) 
e.getLeftExpression();
+
+            modifierManager.processVariableExpression(variableExpression);
+            modifierManager.attachAnnotations(e);
+
+        });
+
+        int size = declarationExpressionList.size();
+        if (size > 0) {
+            DeclarationExpression declarationExpression = 
declarationExpressionList.get(0);
+
+            if (1 == size) {
+                this.configureAST(declarationExpression, ctx);
+            } else {
+                // Tweak start of first declaration
+                declarationExpression.setLineNumber(ctx.getStart().getLine());
+                
declarationExpression.setColumnNumber(ctx.getStart().getCharPositionInLine() + 
1);
+            }
+        }
+
+        return this.configureAST(new 
DeclarationListStatement(declarationExpressionList), ctx);
+    }
+
+    @Override
+    public List<Expression> visitTypeNamePairs(TypeNamePairsContext ctx) {
+        return 
ctx.typeNamePair().stream().map(this::visitTypeNamePair).collect(Collectors.toList());
+    }
+
+    @Override
+    public VariableExpression visitTypeNamePair(TypeNamePairContext ctx) {
+        return this.configureAST(
+                new VariableExpression(
+                        
this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
+                        this.visitType(ctx.type())),
+                ctx);
+    }
+
+    @Override
+    public List<DeclarationExpression> 
visitVariableDeclarators(VariableDeclaratorsContext ctx) {
+        ClassNode variableType = 
ctx.getNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE);
+        Objects.requireNonNull(variableType, "variableType should not be 
null");
+
+        return ctx.variableDeclarator().stream()
+                .map(e -> {
+                    e.putNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE, 
variableType);
+                    return this.visitVariableDeclarator(e);
+//                    return 
this.configureAST(this.visitVariableDeclarator(e), ctx);
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public DeclarationExpression 
visitVariableDeclarator(VariableDeclaratorContext ctx) {
+        ClassNode variableType = 
ctx.getNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE);
+        Objects.requireNonNull(variableType, "variableType should not be 
null");
+
+        org.codehaus.groovy.syntax.Token token;
+        if (asBoolean(ctx.ASSIGN())) {
+            token = createGroovyTokenByType(ctx.ASSIGN().getSymbol(), 
Types.ASSIGN);
+        } else {
+            token = new org.codehaus.groovy.syntax.Token(Types.ASSIGN, 
ASSIGN_STR, ctx.start.getLine(), 1);
+        }
+
+        return this.configureAST(
+                new DeclarationExpression(
+                        this.configureAST(
+                                new VariableExpression(
+                                        
this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
+                                        variableType
+                                ),
+                                ctx.variableDeclaratorId()),
+                        token,
+                        
this.visitVariableInitializer(ctx.variableInitializer())),
+                ctx);
+    }
+
+    @Override
+    public Expression visitVariableInitializer(VariableInitializerContext ctx) 
{
+        if (!asBoolean(ctx)) {
+            return EmptyExpression.INSTANCE;
+        }
+
+        if (asBoolean(ctx.arrayInitializer())) {
+            return 
this.configureAST(this.visitArrayInitializer(ctx.arrayInitializer()), ctx);
+        }
+
+        if (asBoolean(ctx.statementExpression())) {
+            return this.configureAST(
+                    ((ExpressionStatement) 
this.visit(ctx.statementExpression())).getExpression(),
+                    ctx);
+        }
+
+        if (asBoolean(ctx.standardLambda())) {
+            return 
this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
+        }
+
+        throw createParsingFailedException("Unsupported variable initializer: 
" + ctx.getText(), ctx);
+    }
+
+    @Override
+    public ListExpression visitArrayInitializer(ArrayInitializerContext ctx) {
+        return this.configureAST(
+                new ListExpression(
+                        ctx.variableInitializer().stream()
+                                .map(this::visitVariableInitializer)
+                                .collect(Collectors.toList())),
+                ctx);
+    }
+
+
+    @Override
+    public Statement visitBlock(BlockContext ctx) {
+        if (!asBoolean(ctx)) {
+            return this.createBlockStatement();
+        }
+
+        return this.configureAST(
+                this.visitBlockStatementsOpt(ctx.blockStatementsOpt()),
+                ctx);
+    }
+
+
+    @Override
+    public ExpressionStatement visitNormalExprAlt(NormalExprAltContext ctx) {
+        return this.configureAST(new ExpressionStatement((Expression) 
this.visit(ctx.expression())), ctx);
+    }
+
+    @Override
+    public ExpressionStatement visitCommandExprAlt(CommandExprAltContext ctx) {
+        return this.configureAST(new 
ExpressionStatement(this.visitCommandExpression(ctx.commandExpression())), ctx);
+    }
+
+    @Override
+    public Expression visitCommandExpression(CommandExpressionContext ctx) {
+        Expression baseExpr = this.visitPathExpression(ctx.pathExpression());
+        Expression arguments = 
this.visitEnhancedArgumentList(ctx.enhancedArgumentList());
+
+        MethodCallExpression methodCallExpression;
+        if (baseExpr instanceof PropertyExpression) { // e.g. obj.a 1, 2
+            methodCallExpression =
+                    this.configureAST(
+                            this.createMethodCallExpression(
+                                    (PropertyExpression) baseExpr, arguments),
+                            arguments);
+
+        } else if (baseExpr instanceof MethodCallExpression && 
!isTrue(baseExpr, IS_INSIDE_PARENTHESES)) { // e.g. m {} a, b  OR  m(...) a, b
+            if (asBoolean(arguments)) {
+                // The error should never be thrown.
+                throw new GroovyBugError("When baseExpr is a instance of 
MethodCallExpression, which should follow NO argumentList");
+            }
+
+            methodCallExpression = (MethodCallExpression) baseExpr;
+        } else if (
+                !isTrue(baseExpr, IS_INSIDE_PARENTHESES)
+                        && (baseExpr instanceof VariableExpression /* e.g. m 
1, 2 */
+                        || baseExpr instanceof GStringExpression /* e.g. "$m" 
1, 2 */
+                        || (baseExpr instanceof ConstantExpression && 
isTrue(baseExpr, IS_STRING)) /* e.g. "m" 1, 2 */)
+                ) {
+            methodCallExpression =
+                    this.configureAST(
+                            this.createMethodCallExpression(baseExpr, 
arguments),
+                            arguments);
+        } else { // e.g. a[x] b, new A() b, etc.
+            methodCallExpression =
+                    this.configureAST(
+                            new MethodCallExpression(
+                                    baseExpr,
+                                    CALL_STR,
+                                    arguments
+                            ),
+                            arguments
+                    );
+
+            methodCallExpression.setImplicitThis(false);
+        }
+
+        if (!asBoolean(ctx.commandArgument())) {
+            return this.configureAST(methodCallExpression, ctx);
+        }
+
+        return this.configureAST(
+                (Expression) ctx.commandArgument().stream()
+                        .map(e -> (Object) e)
+                        .reduce(methodCallExpression,
+                                (r, e) -> {
+                                    CommandArgumentContext 
commandArgumentContext = (CommandArgumentContext) e;
+                                    
commandArgumentContext.putNodeMetaData(CMD_EXPRESSION_BASE_EXPR, r);
+
+                                    return 
this.visitCommandArgument(commandArgumentContext);
+                                }
+                        ),
+                ctx);
+    }
+
+    @Override
+    public Expression visitCommandArgument(CommandArgumentContext ctx) {
+        // e.g. x y a b     we call "x y" as the base expression
+        Expression baseExpr = ctx.getNodeMetaData(CMD_EXPRESSION_BASE_EXPR);
+
+        Expression primaryExpr = (Expression) this.visit(ctx.primary());
+
+        if (asBoolean(ctx.enhancedArgumentList())) { // e.g. x y a b
+            if (baseExpr instanceof PropertyExpression) { // the branch should 
never reach, because a.b.c will be parsed as a path expression, not a method 
call
+                throw createParsingFailedException("Unsupported command 
argument: " + ctx.getText(), ctx);
+            }
+
+            // the following code will process "a b" of "x y a b"
+            MethodCallExpression methodCallExpression =
+                    new MethodCallExpression(
+                            baseExpr,
+                            this.createConstantExpression(primaryExpr),
+                            
this.visitEnhancedArgumentList(ctx.enhancedArgumentList())
+                    );
+            methodCallExpression.setImplicitThis(false);
+
+            return this.configureAST(methodCallExpression, ctx);
+        } else if (asBoolean(ctx.pathElement())) { // e.g. x y a.b
+            Expression pathExpression =
+                    this.createPathExpression(
+                            this.configureAST(
+                                    new PropertyExpression(baseExpr, 
this.createConstantExpression(primaryExpr)),
+                                    primaryExpr
+                            ),
+                            ctx.pathElement()
+                    );
+
+            return this.configureAST(pathExpression, ctx);
+        }
+
+        // e.g. x y a
+        return this.configureAST(
+                new PropertyExpression(
+                        baseExpr,
+                        primaryExpr instanceof VariableExpression
+                                ? this.createConstantExpression(primaryExpr)
+                                : primaryExpr
+                ),
+                primaryExpr
+        );
+    }
+
+
+    // expression {    
--------------------------------------------------------------------
+
+    @Override
+    public ClassNode visitCastParExpression(CastParExpressionContext ctx) {
+        return this.visitType(ctx.type());
+    }
+
+    @Override
+    public Expression visitParExpression(ParExpressionContext ctx) {
+        Expression expression;
+
+        if (asBoolean(ctx.statementExpression())) {
+            expression = ((ExpressionStatement) 
this.visit(ctx.statementExpression())).getExpression();
+        } else if (asBoolean(ctx.standardLambda())) {
+            expression = this.visitStandardLambda(ctx.standardLambda());
+        } else {
+            throw createParsingFailedException("Unsupported parentheses 
expression: " + ctx.getText(), ctx);
+        }
+
+        expression.putNodeMetaData(IS_INSIDE_PARENTHESES, true);
+
+        return this.configureAST(expression, ctx);
+    }
+
+    @Override
+    public Expression visitPathExpression(PathExpressionContext ctx) {
+        return this.configureAST(
+                this.createPathExpression((Expression) 
this.visit(ctx.primary()), ctx.pathElement()),
+                ctx);
+    }
+
+    @Override
+    public Expression visitPathElement(PathElementContext ctx) {
+        Expression baseExpr = ctx.getNodeMetaData(PATH_EXPRESSION_BASE_EXPR);
+        Objects.requireNonNull(baseExpr, "baseExpr is required!");
+
+        if (asBoolean(ctx.namePart())) {
+            Expression namePartExpr = this.visitNamePart(ctx.namePart());
+            GenericsType[] genericsTypes = 
this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments());
+
+
+            if (asBoolean(ctx.DOT())) {
+                if (asBoolean(ctx.AT())) { // e.g. obj.@a
+                    return this.configureAST(new AttributeExpression(baseExpr, 
namePartExpr), ctx);
+                } else { // e.g. obj.p
+                    PropertyExpression propertyExpression = new 
PropertyExpression(baseExpr, namePartExpr);
+                    
propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, 
genericsTypes);
+
+                    return this.configureAST(propertyExpression, ctx);
+                }
+            } else if (asBoolean(ctx.SAFE_DOT())) {
+                if (asBoolean(ctx.AT())) { // e.g. obj?.@a
+                    return this.configureAST(new AttributeExpression(baseExpr, 
namePartExpr, true), ctx);
+                } else { // e.g. obj?.p
+                    PropertyExpression propertyExpression = new 
PropertyExpression(baseExpr, namePartExpr, true);
+                    
propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, 
genericsTypes);
+
+                    return this.configureAST(propertyExpression, ctx);
+                }
+            } else if (asBoolean(ctx.METHOD_POINTER())) { // e.g. obj.&m
+                return this.configureAST(new MethodPointerExpression(baseExpr, 
namePartExpr), ctx);
+            } else if (asBoolean(ctx.METHOD_REFERENCE())) { // e.g. obj::m
+                return this.configureAST(new 
MethodReferenceExpression(baseExpr, namePartExpr), ctx);
+            } else if (asBoolean(ctx.SPREAD_DOT())) {
+                if (asBoolean(ctx.AT())) { // e.g. obj*.@a
+                    AttributeExpression attributeExpression = new 
AttributeExpression(baseExpr, namePartExpr, true);
+
+                    attributeExpression.setSpreadSafe(true);
+
+                    return this.configureAST(attributeExpression, ctx);
+                } else { // e.g. obj*.p
+                    PropertyExpression propertyExpression = new 
PropertyExpression(baseExpr, namePartExpr, true);
+                    
propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, 
genericsTypes);
+
+                    propertyExpression.setSpreadSafe(true);
+
+                    return this.configureAST(propertyExpression, ctx);
+                }
+            }
+        }
+
+        if (asBoolean(ctx.indexPropertyArgs())) { // e.g. list[1, 3, 5]
+            Pair<Token, Expression> pair = 
this.visitIndexPropertyArgs(ctx.indexPropertyArgs());
+
+            return this.configureAST(
+                    new BinaryExpression(baseExpr, 
createGroovyToken(pair.getKey()), pair.getValue(), 
asBoolean(ctx.indexPropertyArgs().QUESTION())),
+                    ctx);
+        }
+
+        if (asBoolean(ctx.namedPropertyArgs())) { // this is a special way to 
new instance, e.g. Person(name: 'Daniel.Sun', location: 'Shanghai')
+            List<MapEntryExpression> mapEntryExpressionList =
+                    this.visitNamedPropertyArgs(ctx.namedPropertyArgs());
+
+            Expression right;
+            if (mapEntryExpressionList.size() == 1) {
+                MapEntryExpression mapEntryExpression = 
mapEntryExpressionList.get(0);
+
+                if (mapEntryExpression.getKeyExpression() instanceof 
SpreadMapExpression) {
+                    right = mapEntryExpression.getKeyExpression();
+                } else {
+                    right = mapEntryExpression;
+                }
+            } else {
+                ListExpression listExpression =
+                        this.configureAST(
+                                new ListExpression(
+                                        mapEntryExpressionList.stream()
+                                                .map(
+                                                        e -> {
+                                                            if 
(e.getKeyExpression() instanceof SpreadMapExpression) {
+                                                                return 
e.getKeyExpression();
+                                                            }
+
+                                                            return e;
+                                                        }
+                                                )
+                                                .collect(Collectors.toList())),
+                                ctx.namedPropertyArgs()
+                        );
+                listExpression.setWrapped(true);
+                right = listExpression;
+            }
+
+            return this.configureAST(
+                    new BinaryExpression(baseExpr, 
createGroovyToken(ctx.namedPropertyArgs().LBRACK().getSymbol()), right),
+                    ctx);
+        }
+
+        if (asBoolean(ctx.arguments())) {
+            Expression argumentsExpr = this.visitArguments(ctx.arguments());
+
+            if (isTrue(baseExpr, IS_INSIDE_PARENTHESES)) { // e.g. (obj.x)(), 
(obj.@x)()
+                MethodCallExpression methodCallExpression =
+                        new MethodCallExpression(
+                                baseExpr,
+                                CALL_STR,
+                                argumentsExpr
+                        );
+
+                methodCallExpression.setImplicitThis(false);
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+            if (baseExpr instanceof AttributeExpression) { // e.g. obj.@a(1, 2)
+                AttributeExpression attributeExpression = 
(AttributeExpression) baseExpr;
+                attributeExpression.setSpreadSafe(false); // whether 
attributeExpression is spread safe or not, we must reset it as false
+
+                MethodCallExpression methodCallExpression =
+                        new MethodCallExpression(
+                                attributeExpression,
+                                CALL_STR,
+                                argumentsExpr
+                        );
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+            if (baseExpr instanceof PropertyExpression) { // e.g. obj.a(1, 2)
+                MethodCallExpression methodCallExpression =
+                        this.createMethodCallExpression((PropertyExpression) 
baseExpr, argumentsExpr);
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+            if (baseExpr instanceof VariableExpression) { // void and 
primitive type AST node must be an instance of VariableExpression
+                String baseExprText = baseExpr.getText();
+                if (VOID_STR.equals(baseExprText)) { // e.g. void()
+                    MethodCallExpression methodCallExpression =
+                            new MethodCallExpression(
+                                    this.createConstantExpression(baseExpr),
+                                    CALL_STR,
+                                    argumentsExpr
+                            );
+
+                    methodCallExpression.setImplicitThis(false);
+
+                    return this.configureAST(methodCallExpression, ctx);
+                } else if (PRIMITIVE_TYPE_SET.contains(baseExprText)) { // 
e.g. int(), long(), float(), etc.
+                    throw createParsingFailedException("Primitive type 
literal: " + baseExprText + " cannot be used as a method name", ctx);
+                }
+            }
+
+            if (baseExpr instanceof VariableExpression
+                    || baseExpr instanceof GStringExpression
+                    || (baseExpr instanceof ConstantExpression && 
isTrue(baseExpr, IS_STRING))) { // e.g. m(), "$m"(), "m"()
+
+                String baseExprText = baseExpr.getText();
+                if (SUPER_STR.equals(baseExprText) || 
THIS_STR.equals(baseExprText)) { // e.g. this(...), super(...)
+                    // class declaration is not allowed in the closure,
+                    // so if this and super is inside the closure, it will not 
be constructor call.
+                    // e.g. 
src/test/org/codehaus/groovy/transform/MapConstructorTransformTest.groovy:
+                    // @MapConstructor(pre={ super(args?.first, args?.last); 
args = args ?: [:] }, post = { first = first?.toUpperCase() })
+                    if (ctx.isInsideClosure) {
+                        return this.configureAST(
+                                new MethodCallExpression(
+                                        baseExpr,
+                                        baseExprText,
+                                        argumentsExpr
+                                ),
+                                ctx);
+                    }
+
+                    return this.configureAST(
+                            new ConstructorCallExpression(
+                                    SUPER_STR.equals(baseExprText)
+                                            ? ClassNode.SUPER
+                                            : ClassNode.THIS,
+                                    argumentsExpr
+                            ),
+                            ctx);
+                }
+
+                MethodCallExpression methodCallExpression =
+                        this.createMethodCallExpression(baseExpr, 
argumentsExpr);
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+            // e.g. 1(), 1.1(), ((int) 1 / 2)(1, 2), {a, b -> a + b }(1, 2), 
m()()
+            MethodCallExpression methodCallExpression =
+                    new MethodCallExpression(baseExpr, CALL_STR, 
argumentsExpr);
+            methodCallExpression.setImplicitThis(false);
+
+            return this.configureAST(methodCallExpression, ctx);
+        }
+
+        if (asBoolean(ctx.closure())) {
+            ClosureExpression closureExpression = 
this.visitClosure(ctx.closure());
+
+            if (baseExpr instanceof MethodCallExpression) {
+                MethodCallExpression methodCallExpression = 
(MethodCallExpression) baseExpr;
+                Expression argumentsExpression = 
methodCallExpression.getArguments();
+
+                if (argumentsExpression instanceof ArgumentListExpression) { 
// normal arguments, e.g. 1, 2
+                    ArgumentListExpression argumentListExpression = 
(ArgumentListExpression) argumentsExpression;
+                    
argumentListExpression.getExpressions().add(closureExpression);
+
+                    return this.configureAST(methodCallExpression, ctx);
+                }
+
+                if (argumentsExpression instanceof TupleExpression) { // named 
arguments, e.g. x: 1, y: 2
+                    TupleExpression tupleExpression = (TupleExpression) 
argumentsExpression;
+                    NamedArgumentListExpression namedArgumentListExpression = 
(NamedArgumentListExpression) tupleExpression.getExpression(0);
+
+                    if (asBoolean(tupleExpression.getExpressions())) {
+                        methodCallExpression.setArguments(
+                                this.configureAST(
+                                        new ArgumentListExpression(
+                                                Stream.of(
+                                                        this.configureAST(
+                                                                new 
MapExpression(namedArgumentListExpression.getMapEntryExpressions()),
+                                                                
namedArgumentListExpression
+                                                        ),
+                                                        closureExpression
+                                                ).collect(Collectors.toList())
+                                        ),
+                                        tupleExpression
+                                )
+                        );
+                    } else {
+                        // the branch should never reach, because named 
arguments must not be empty
+                        methodCallExpression.setArguments(
+                                this.configureAST(
+                                        new 
ArgumentListExpression(closureExpression),
+                                        tupleExpression));
+                    }
+
+
+                    return this.configureAST(methodCallExpression, ctx);
+                }
+
+            }
+
+            // e.g. 1 {}, 1.1 {}
+            if (baseExpr instanceof ConstantExpression && isTrue(baseExpr, 
IS_NUMERIC)) {
+                MethodCallExpression methodCallExpression =
+                        new MethodCallExpression(
+                                baseExpr,
+                                CALL_STR,
+                                this.configureAST(
+                                        new 
ArgumentListExpression(closureExpression),
+                                        closureExpression
+                                )
+                        );
+                methodCallExpression.setImplicitThis(false);
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+
+            if (baseExpr instanceof PropertyExpression) { // e.g. obj.m {  }
+                PropertyExpression propertyExpression = (PropertyExpression) 
baseExpr;
+
+                MethodCallExpression methodCallExpression =
+                        this.createMethodCallExpression(
+                                propertyExpression,
+                                this.configureAST(
+                                        new 
ArgumentListExpression(closureExpression),
+                                        closureExpression
+                                )
+                        );
+
+                return this.configureAST(methodCallExpression, ctx);
+            }
+
+            // e.g.  m { return 1; }
+            MethodCallExpression methodCallExpression =
+                    new MethodCallExpression(
+                            VariableExpression.THIS_EXPRESSION,
+
+                            (baseExpr instanceof VariableExpression)
+                                    ? 
this.createConstantExpression((VariableExpression) baseExpr)
+                                    : baseExpr,
+
+                            this.configureAST(
+                                    new 
ArgumentListExpression(closureExpression),
+                                    closureExpression)
+                    );
+
+
+            return this.configureAST(methodCallExpression, ctx);
+        }
+
+        throw createParsingFailedException("Unsupported path element: " + 
ctx.getText(), ctx);
+    }
+
+
+    @Override
+    public GenericsType[] 
visitNonWildcardTypeArguments(NonWildcardTypeArgumentsContext ctx) {
+        if (!asBoolean(ctx)) {
+            return null;
+        }
+
+        return Arrays.stream(this.visitTypeList(ctx.typeList()))
+                .map(this::createGenericsType)
+                .toArray(GenericsType[]::new);
+    }
+
+    @Override
+    public ClassNode[] visitTypeList(TypeListContext ctx) {
+        if (!asBoolean(ctx)) {
+            return new ClassNode[0];
+        }
+
+        return ctx.type().stream()
+                .map(this::visitType)
+                .toArray(ClassNode[]::new);
+    }
+
+    @Override
+    public Expression visitArguments(ArgumentsContext ctx) {
+        if (!asBoolean(ctx) || !asBoolean(ctx.enhancedArgumentList())) {
+            return new ArgumentListExpression();
+        }
+
+        return 
this.configureAST(this.visitEnhancedArgumentList(ctx.enhancedArgumentList()), 
ctx);
+    }
+
+    @Override
+    public Expression visitEnhancedArgumentList(EnhancedArgumentListContext 
ctx) {
+        if (!asBoolean(ctx)) {
+            return null;
+        }
+
+        List<Expression> expressionList = new LinkedList<>();
+        List<MapEntryExpression> mapEntryExpressionList = new LinkedList<>();
+
+        ctx.enhancedArgumentListElement().stream()
+                .map(this::visitEnhancedArgumentListElement)
+                .forEach(e -> {
+
+                    if (e instanceof MapEntryExpression) {
+                        mapEntryExpressionList.add((MapEntryExpression) e);
+                    } else {
+                        expressionList.add(e);
+                    }
+                });
+
+        if (!asBoolean(mapEntryExpressionList)) { // e.g. arguments like  1, 2 
OR  someArg, e -> e
+            return this.configureAST(
+                    new ArgumentListExpression(expressionList),
+                    ctx);
+        }
+
+        if (!asBoolean(expressionList)) { // e.g. arguments like  x: 1, y: 2
+            return this.configureAST(
+                    new TupleExpression(
+                            this.configureAST(
+                                    new 
NamedArgumentListExpression(mapEntryExpressionList),
+                                    ctx)),
+                    ctx);
+        }
+
+        if (asBoolean(mapEntryExpressionList) && asBoolean(expressionList)) { 
// e.g. arguments like x: 1, 'a', y: 2, 'b', z: 3
+            ArgumentListExpression argumentListExpression = new 
ArgumentListExpression(expressionList);
+            argumentListExpression.getExpressions().add(0, new 
MapExpression(mapEntryExpressionList)); // TODO: confirm BUG OR NOT? All map 
entries will be put at first, which is not friendly to Groovy developers
+
+            return this.configureAST(argumentListExpression, ctx);
+        }
+
+        throw createParsingFailedException("Unsupported argument list: " + 
ctx.getText(), ctx);
+    }
+
+    @Override
+    public Expression 
visitEnhancedArgumentListElement(EnhancedArgumentListElementContext ctx) {
+        if (asBoolean(ctx.expressionListElement())) {
+            return 
this.configureAST(this.visitExpressionListElement(ctx.expressionListElement()), 
ctx);
+        }
+
+        if (asBoolean(ctx.standardLambda())) {
+            return 
this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
+        }
+
+        if (asBoolean(ctx.mapEntry())) {
+            return this.configureAST(this.visitMapEntry(ctx.mapEntry()), ctx);
+        }
+
+        throw createParsingFailedException("Unsupported enhanced argument list 
element: " + ctx.getText(), ctx);
+    }
+
+
+    @Override
+    public ConstantExpression visitStringLiteral(StringLiteralContext ctx) {
+        String text = ctx.StringLiteral().getText();
+
+        int slashyType = text.startsWith("/") ? StringUtils.SLASHY :
+                text.startsWith("$/") ? StringUtils.DOLLAR_SLASHY : 
StringUtils.NONE_SLASHY;
+
+        if (text.startsWith("'''") || text.startsWith("\"\"\"")) {
+            text = StringUtils.removeCR(text); // remove CR in the multiline 
string
+
+            text = text.length() == 6 ? "" : text.substring(3, text.length() - 
3);
+        } else if (text.startsWith("'") || text.startsWith("/") || 
text.startsWith("\"")) {
+            if (text.startsWith("/")) { // the slashy string can span rows, so 
we have to remove CR for it
+                text = StringUtils.removeCR(text); // remove CR in the 
multiline string
+            }
+
+            text = text.length() == 2 ? "" : text.substring(1, text.length() - 
1);
+        } else if (text.startsWith("$/")) {
+            text = StringUtils.removeCR(text);
+
+            text = text.length() == 4 ? "" : text.substring(2, text.length() - 
2);
+        }
+
+        //handle escapes.
+        text = StringUtils.replaceEscapes(text, slashyType);
+
+        ConstantExpression constantExpression = new ConstantExpression(text, 
true);
+        constantExpression.putNodeMetaData(IS_STRING, true);
+
+        return this.configureAST(constantExpression, ctx);
+    }
+
+
+    @Override
+    public Pair<Token, Expression> 
visitIndexPropertyArgs(IndexPropertyArgsContext ctx) {
+        List<Expression> expressionList = 
this.visitExpressionList(ctx.expressionList());
+
+
+        if (expressionList.size() == 1) {
+            Expression expr = expressionList.get(0);
+
+            Expression indexExpr;
+            if (expr instanceof SpreadExpression) { // e.g. a[*[1, 2]]
+                ListExpression listExpression = new 
ListExpression(expressionList);
+                listExpression.setWrapped(false);
+
+                indexExpr = listExpression;
+            } else { // e.g. a[1]
+                indexExpr = expr;
+            }
+
+            return new Pair<>(ctx.LBRACK().getSymbol(), indexExpr);
+        }
+
+        // e.g. a[1, 2]
+        ListExpression listExpression = new Lis

<TRUNCATED>

Reply via email to