All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.groovy.parser.antlr4.AstBuilder Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha-11
Show newest version
/*
 *  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.Tuple2;
import groovy.lang.Tuple3;
import groovy.transform.CompileStatic;
import groovy.transform.NonSealed;
import groovy.transform.Sealed;
import groovy.transform.Trait;
import groovy.transform.TupleConstructor;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.Interval;
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.DescriptiveErrorStrategy;
import org.apache.groovy.parser.antlr4.internal.atnmanager.AtnManager;
import org.apache.groovy.parser.antlr4.util.StringUtils;
import org.apache.groovy.util.Maps;
import org.apache.groovy.util.SystemUtil;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.antlr.EnumHelper;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CodeVisitorSupport;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.EnumConstantClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModifierNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.NodeMetaDataHandler;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.AttributeExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BitwiseNegationExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ClosureListExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
import org.codehaus.groovy.ast.expr.EmptyExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.LambdaExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.MethodPointerExpression;
import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.NotExpression;
import org.codehaus.groovy.ast.expr.PostfixExpression;
import org.codehaus.groovy.ast.expr.PrefixExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.RangeExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import org.codehaus.groovy.ast.expr.SpreadMapExpression;
import org.codehaus.groovy.ast.expr.TernaryExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.UnaryMinusExpression;
import org.codehaus.groovy.ast.expr.UnaryPlusExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.BreakStatement;
import org.codehaus.groovy.ast.stmt.CaseStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.ContinueStatement;
import org.codehaus.groovy.ast.stmt.DoWhileStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.SwitchStatement;
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.tools.ClosureUtils;
import org.codehaus.groovy.classgen.Verifier;
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.DefaultGroovyMethods;
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.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static groovy.lang.Tuple.tuple;
import static org.apache.groovy.parser.antlr4.GroovyParser.*;
import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureAST;
import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureEndPosition;
import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
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.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
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.runtime.DefaultGroovyMethods.asBoolean;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;

/**
 * Builds the AST from the parse tree generated by Antlr4.
 */
public class AstBuilder extends GroovyParserBaseVisitor {

    public AstBuilder(final SourceUnit sourceUnit, final boolean groovydocEnabled, final boolean runtimeGroovydocEnabled) {
        this.sourceUnit = sourceUnit;
        this.moduleNode = new ModuleNode(sourceUnit);
        CharStream charStream = createCharStream(sourceUnit);

        this.lexer = new GroovyLangLexer(charStream);
        this.parser = new GroovyLangParser(new CommonTokenStream(this.lexer));
        this.parser.setErrorHandler(new DescriptiveErrorStrategy(charStream));

        this.groovydocManager = new GroovydocManager(groovydocEnabled, runtimeGroovydocEnabled);
        this.tryWithResourcesASTTransformation = new TryWithResourcesASTTransformation(this);
    }

    private CharStream createCharStream(final SourceUnit sourceUnit) {
        CharStream charStream;

        try {
            charStream = CharStreams.fromReader(
                    new BufferedReader(sourceUnit.getSource().getReader()),
                    sourceUnit.getName());
        } catch (IOException e) {
            throw new RuntimeException("Error occurred when reading source code.", e);
        }

        return charStream;
    }

    private GroovyParserRuleContext buildCST() throws CompilationFailedException {
        GroovyParserRuleContext result;

        try {
            // parsing have to wait util clearing is complete.
            AtnManager.READ_LOCK.lock();
            try {
                final TokenStream tokenStream = parser.getInputStream();
                if (SLL_THRESHOLD >= 0 && tokenStream.size() > SLL_THRESHOLD) {
                    // The more tokens to parse, the more possibility SLL will fail and the more parsing time will waste.
                    // The option `groovy.antlr4.sll.threshold` could be tuned for better parsing performance, but it is disabled by default.
                    // If the token count is greater than `groovy.antlr4.sll.threshold`, use LL directly.
                    result = buildCST(PredictionMode.LL);
                } else {
                    try {
                        result = buildCST(PredictionMode.SLL);
                    } catch (Throwable t) {
                        // if some syntax error occurred in the lexer, no need to retry the powerful LL mode
                        if (t instanceof GroovySyntaxError && GroovySyntaxError.LEXER == ((GroovySyntaxError) t).getSource()) {
                            throw t;
                        }

                        tokenStream.seek(0);
                        result = buildCST(PredictionMode.LL);
                    }
                }
            } finally {
                AtnManager.READ_LOCK.unlock();
            }
        } catch (Throwable t) {
            throw convertException(t);
        }

        return result;
    }

    private GroovyParserRuleContext buildCST(final PredictionMode predictionMode) {
        parser.getInterpreter().setPredictionMode(predictionMode);

        if (PredictionMode.SLL.equals(predictionMode)) {
            this.removeErrorListeners();
        } else {
            this.addErrorListeners();
        }

        return parser.compilationUnit();
    }

    private CompilationFailedException convertException(final Throwable t) {
        CompilationFailedException cfe;

        if (t instanceof CompilationFailedException) {
            cfe = (CompilationFailedException) t;
        } else if (t instanceof ParseCancellationException) {
            cfe = createParsingFailedException(t.getCause());
        } else {
            cfe = createParsingFailedException(t);
        }

        return cfe;
    }

    public ModuleNode buildAST() {
        try {
            return (ModuleNode) this.visit(this.buildCST());
        } catch (Throwable t) {
            throw convertException(t);
        }
    }

    @Override
    public ModuleNode visitCompilationUnit(final CompilationUnitContext ctx) {
        this.visit(ctx.packageDeclaration());

        for (ASTNode node : this.visitScriptStatements(ctx.scriptStatements())) {
            if (node instanceof DeclarationListStatement) { // local variable declaration(s)
                for (Statement stmt: ((DeclarationListStatement) node).getDeclarationStatements()) {
                    this.moduleNode.addStatement(stmt);
                }
            } else if (node instanceof Statement) {
                this.moduleNode.addStatement((Statement) node);
            } else if (node instanceof MethodNode) {
                this.moduleNode.addMethod((MethodNode) node);
            }
        }

        for (ClassNode node : this.classNodeList) {
            this.moduleNode.addClass(node);
        }

        if (this.isPackageInfoDeclaration()) {
            ClassNode packageInfo = ClassHelper.make(this.moduleNode.getPackageName() + PACKAGE_INFO);
            if (!this.moduleNode.getClasses().contains(packageInfo)) {
                this.moduleNode.addClass(packageInfo);
            }
        } else if (this.isBlankScript()) {
            // add "return null" if script has no statements/methods/classes
            this.moduleNode.addStatement(ReturnStatement.RETURN_NULL_OR_VOID);
        }

        this.configureScriptClassNode();

        if (this.numberFormatError != null) {
            throw createParsingFailedException(this.numberFormatError.getV2().getMessage(), this.numberFormatError.getV1());
        }

        return this.moduleNode;
    }

    @Override
    public List visitScriptStatements(final ScriptStatementsContext ctx) {
        if (!asBoolean(ctx)) {
            return Collections.emptyList();
        }

        return ctx.scriptStatement().stream()
                .map(e -> (ASTNode) visit(e))
                .collect(Collectors.toList());
    }

    @Override
    public PackageNode visitPackageDeclaration(final PackageDeclarationContext ctx) {
        String packageName = this.visitQualifiedName(ctx.qualifiedName());
        moduleNode.setPackageName(packageName + DOT_STR);

        PackageNode packageNode = moduleNode.getPackage();

        packageNode.addAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt()));

        return configureAST(packageNode, ctx);
    }

    @Override
    public ImportNode visitImportDeclaration(final ImportDeclarationContext ctx) {
        List annotations = this.visitAnnotationsOpt(ctx.annotationsOpt());

        boolean hasStatic = asBoolean(ctx.STATIC());
        boolean hasStar   = asBoolean(ctx.MUL());
        boolean hasAlias  = asBoolean(ctx.alias);

        ImportNode importNode;

        if (hasStatic) {
            if (hasStar) { // e.g. import static java.lang.Math.*
                String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
                ClassNode importType = makeClassNode(qualifiedName);
                configureAST(importType, ctx.qualifiedName());

                moduleNode.addStaticStarImport(importType.getText(), importType, annotations);
                importNode = last(moduleNode.getStaticStarImports().values());
            } else { // e.g. import static java.lang.Math.pow
                List identifierList = ctx.qualifiedName().qualifiedNameElement();
                int identifierListSize = identifierList.size();

                String qualifiedName = identifierList.stream().limit(identifierListSize - 1).map(ParseTree::getText).collect(Collectors.joining(DOT_STR));
                ClassNode importType = makeClassNode(qualifiedName);
                configureAST(importType, ctx.qualifiedName()); // qualifiedName() includes member name
                configureEndPosition(importType, identifierList.get(Math.max(0, identifierListSize - 2)).getStop());

                String memberName = identifierList.get(identifierListSize - 1).getText();
                String simpleName = hasAlias ? ctx.alias.getText() : memberName;

                moduleNode.addStaticImport(importType, memberName, simpleName, annotations);
                importNode = last(moduleNode.getStaticImports().values());
            }
        } else {
            if (hasStar) { // e.g. import java.util.*
                String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
                moduleNode.addStarImport(qualifiedName + DOT_STR, annotations);
                importNode = last(moduleNode.getStarImports());
            } else { // e.g. import java.util.Map
                String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
                ClassNode importType = makeClassNode(qualifiedName);
                configureAST(importType, ctx.qualifiedName());

                String simpleName = hasAlias ? ctx.alias.getText()
                                             : last(ctx.qualifiedName().qualifiedNameElement()).getText();

                moduleNode.addImport(simpleName, importType, annotations);
                importNode = last(moduleNode.getImports());
            }
        }

        return configureAST(importNode, ctx);
    }

    private static AnnotationNode makeAnnotationNode(final Class type) {
        AnnotationNode node = new AnnotationNode(ClassHelper.make(type));
        // TODO: source offsets
        return node;
    }

    private static ClassNode makeClassNode(final String name) {
        ClassNode node = ClassHelper.make(name);
        // TODO: shared instances
        return node;
    }

    // statement { -------------------------------------------------------------

    @Override
    public AssertStatement visitAssertStatement(final AssertStatementContext ctx) {
        visitingAssertStatementCount += 1;

        Expression conditionExpression = (Expression) this.visit(ctx.ce);

        if (conditionExpression instanceof BinaryExpression) {
            BinaryExpression binaryExpression = (BinaryExpression) conditionExpression;

            if (binaryExpression.getOperation().getType() == Types.ASSIGN) {
                throw createParsingFailedException("Assignment expression is not allowed in the assert statement", conditionExpression);
            }
        }

        BooleanExpression booleanExpression =
                configureAST(
                        new BooleanExpression(conditionExpression), conditionExpression);

        if (!asBoolean(ctx.me)) {
            return configureAST(
                    new AssertStatement(booleanExpression), ctx);
        }

        AssertStatement result = configureAST(new AssertStatement(booleanExpression,
                        (Expression) this.visit(ctx.me)),
                ctx);

        visitingAssertStatementCount -= 1;

        return result;
    }

    @Override
    public Statement visitConditionalStatement(final ConditionalStatementContext ctx) {
        if (asBoolean(ctx.ifElseStatement())) {
            return configureAST(this.visitIfElseStatement(ctx.ifElseStatement()), ctx);
        } else if (asBoolean(ctx.switchStatement())) {
            return configureAST(this.visitSwitchStatement(ctx.switchStatement()), ctx);
        }

        throw createParsingFailedException("Unsupported conditional statement", ctx);
    }

    @Override
    public IfStatement visitIfElseStatement(final IfElseStatementContext ctx) {
        Expression conditionExpression = this.visitExpressionInPar(ctx.expressionInPar());
        BooleanExpression booleanExpression =
                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 configureAST(new IfStatement(booleanExpression, ifBlock, elseBlock), ctx);
    }

    @Override
    public Statement visitLoopStmtAlt(final LoopStmtAltContext ctx) {
        switchExpressionRuleContextStack.push(ctx);
        visitingLoopStatementCount += 1;
        try {
            return configureAST((Statement) this.visit(ctx.loopStatement()), ctx);
        } finally {
            switchExpressionRuleContextStack.pop();
            visitingLoopStatementCount -= 1;
        }
    }

    @Override
    public ForStatement visitForStmtAlt(final ForStmtAltContext ctx) {
        Tuple2 controlTuple = this.visitForControl(ctx.forControl());

        Statement loopBlock = this.unpackStatement((Statement) this.visit(ctx.statement()));

        return configureAST(
                new ForStatement(controlTuple.getV1(), controlTuple.getV2(), asBoolean(loopBlock) ? loopBlock : EmptyStatement.INSTANCE),
                ctx);
    }

    @Override
    public Tuple2 visitForControl(final ForControlContext ctx) {
        if (asBoolean(ctx.enhancedForControl())) { // e.g. for(int i in 0..<10) {}
            return this.visitEnhancedForControl(ctx.enhancedForControl());
        }

        if (asBoolean(ctx.classicalForControl())) { // e.g. for(int i = 0; i < 10; i++) {}
            return this.visitClassicalForControl(ctx.classicalForControl());
        }

        throw createParsingFailedException("Unsupported for control: " + ctx.getText(), ctx);
    }

    @Override
    public Expression visitForInit(final ForInitContext ctx) {
        if (!asBoolean(ctx)) {
            return EmptyExpression.INSTANCE;
        }

        if (asBoolean(ctx.localVariableDeclaration())) {
            DeclarationListStatement declarationListStatement = this.visitLocalVariableDeclaration(ctx.localVariableDeclaration());
            List declarationExpressions = declarationListStatement.getDeclarationExpressions();

            if (declarationExpressions.size() == 1) {
                return configureAST((Expression) declarationExpressions.get(0), ctx);
            } else {
                return configureAST(new ClosureListExpression((List) declarationExpressions), ctx);
            }
        }

        if (asBoolean(ctx.expressionList())) {
            return this.translateExpressionList(ctx.expressionList());
        }

        throw createParsingFailedException("Unsupported for init: " + ctx.getText(), ctx);
    }

    @Override
    public Expression visitForUpdate(final ForUpdateContext ctx) {
        if (!asBoolean(ctx)) {
            return EmptyExpression.INSTANCE;
        }

        return this.translateExpressionList(ctx.expressionList());
    }

    private Expression translateExpressionList(final ExpressionListContext ctx) {
        List expressionList = this.visitExpressionList(ctx);

        if (expressionList.size() == 1) {
            return configureAST(expressionList.get(0), ctx);
        } else {
            return configureAST(new ClosureListExpression(expressionList), ctx);
        }
    }

    @Override
    public Tuple2 visitEnhancedForControl(final EnhancedForControlContext ctx) {
        Parameter parameter = new Parameter(this.visitType(ctx.type()), this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName());
        ModifierManager modifierManager = new ModifierManager(this, this.visitVariableModifiersOpt(ctx.variableModifiersOpt()));
        modifierManager.processParameter(parameter);
        configureAST(parameter, ctx.variableDeclaratorId());
        return tuple(parameter, (Expression) this.visit(ctx.expression()));
    }

    @Override
    public Tuple2 visitClassicalForControl(final ClassicalForControlContext ctx) {
        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 tuple(ForStatement.FOR_LOOP_DUMMY, closureListExpression);
    }

    @Override
    public WhileStatement visitWhileStmtAlt(final WhileStmtAltContext ctx) {
        Tuple2 conditionAndBlock = createLoopConditionExpressionAndBlock(ctx.expressionInPar(), ctx.statement());

        return configureAST(
                new WhileStatement(conditionAndBlock.getV1(), asBoolean(conditionAndBlock.getV2()) ? conditionAndBlock.getV2() : EmptyStatement.INSTANCE),
                ctx);
    }

    @Override
    public DoWhileStatement visitDoWhileStmtAlt(final DoWhileStmtAltContext ctx) {
        Tuple2 conditionAndBlock = createLoopConditionExpressionAndBlock(ctx.expressionInPar(), ctx.statement());

        return configureAST(
                new DoWhileStatement(conditionAndBlock.getV1(), asBoolean(conditionAndBlock.getV2()) ? conditionAndBlock.getV2() : EmptyStatement.INSTANCE),
                ctx);
    }

    private Tuple2 createLoopConditionExpressionAndBlock(final ExpressionInParContext eipc, final StatementContext sc) {
        Expression conditionExpression = this.visitExpressionInPar(eipc);

        BooleanExpression booleanExpression =
                configureAST(
                        new BooleanExpression(conditionExpression),
                        conditionExpression
                );

        Statement loopBlock = this.unpackStatement((Statement) this.visit(sc));

        return tuple(booleanExpression, loopBlock);
    }

    @Override
    public Statement visitTryCatchStatement(final TryCatchStatementContext ctx) {
        boolean resourcesExists = asBoolean(ctx.resources());
        boolean catchExists = asBoolean(ctx.catchClause());
        boolean finallyExists = asBoolean(ctx.finallyBlock());

        if (!(resourcesExists || catchExists || finallyExists)) {
            throw createParsingFailedException("Either a catch or finally clause or both is required for a try-catch-finally statement", ctx);
        }

        TryCatchStatement tryCatchStatement =
                new TryCatchStatement((Statement) this.visit(ctx.block()),
                        this.visitFinallyBlock(ctx.finallyBlock()));

        if (resourcesExists) {
            this.visitResources(ctx.resources()).forEach(tryCatchStatement::addResource);
        }

        ctx.catchClause().stream().map(this::visitCatchClause)
                .reduce(new LinkedList<>(), (r, e) -> {
                    r.addAll(e); // merge several LinkedList instances into one LinkedList instance
                    return r;
                })
                .forEach(tryCatchStatement::addCatch);

        return configureAST(
                tryWithResourcesASTTransformation.transform(
                        configureAST(tryCatchStatement, ctx)),
                ctx);
    }

    @Override
    public List visitResources(final ResourcesContext ctx) {
        return this.visitResourceList(ctx.resourceList());
    }

    @Override
    public List visitResourceList(final ResourceListContext ctx) {
        return ctx.resource().stream().map(this::visitResource).collect(Collectors.toList());
    }

    @Override
    public ExpressionStatement visitResource(final ResourceContext ctx) {
        if (asBoolean(ctx.localVariableDeclaration())) {
            List 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);
        } else if (asBoolean(ctx.expression())) {
            Expression expression = (Expression) this.visit(ctx.expression());
            boolean isVariableDeclaration = expression instanceof BinaryExpression
                    && Types.ASSIGN == ((BinaryExpression) expression).getOperation().getType()
                    && ((BinaryExpression) expression).getLeftExpression() instanceof VariableExpression;
            boolean isVariableAccess = expression instanceof VariableExpression;

            if (!(isVariableDeclaration || isVariableAccess)) {
                throw createParsingFailedException("Only variable declarations or variable access are allowed to declare resource", ctx);
            }
            BinaryExpression assignmentExpression;

            if (isVariableDeclaration) {
                assignmentExpression = (BinaryExpression) expression;
            } else if (isVariableAccess) {
                assignmentExpression = tryWithResourcesASTTransformation.transformResourceAccess(expression);
            } else {
                throw createParsingFailedException("Unsupported resource declaration", ctx);
            }

            return configureAST(
                    new ExpressionStatement(
                            configureAST(
                                    new DeclarationExpression(
                                            configureAST(
                                                    new VariableExpression(assignmentExpression.getLeftExpression().getText()),
                                                    assignmentExpression.getLeftExpression()
                                            ),
                                            assignmentExpression.getOperation(),
                                            assignmentExpression.getRightExpression()
                                    ), ctx)
                    ), ctx);
        }

        throw createParsingFailedException("Unsupported resource declaration: " + ctx.getText(), ctx);
    }

    /**
     * Multi-catch(1..*) clause will be unpacked to several normal catch clauses, so the return type is List
     *
     * @param ctx the parse tree
     * @return a list of CatchStatement instances
     */
    @Override
    public List visitCatchClause(final CatchClauseContext ctx) {
        return this.visitCatchType(ctx.catchType()).stream()
                .map(e -> configureAST(
                        new CatchStatement(
                                new Parameter(e, this.visitIdentifier(ctx.identifier())),
                                this.visitBlock(ctx.block())),
                        ctx))
                .collect(Collectors.toList());
    }

    @Override
    public List visitCatchType(final CatchTypeContext ctx) {
        if (!asBoolean(ctx)) {
            return Collections.singletonList(ClassHelper.dynamicType());
        }

        return ctx.qualifiedClassName().stream()
                .map(this::visitQualifiedClassName)
                .collect(Collectors.toList());
    }

    @Override
    public Statement visitFinallyBlock(final FinallyBlockContext ctx) {
        if (!asBoolean(ctx)) {
            return EmptyStatement.INSTANCE;
        }

        return configureAST(
                this.createBlockStatement((Statement) this.visit(ctx.block())),
                ctx);
    }

    @Override
    public SwitchStatement visitSwitchStatement(final SwitchStatementContext ctx) {
        switchExpressionRuleContextStack.push(ctx);
        visitingSwitchStatementCount += 1;
        try {
            List statementList =
                    ctx.switchBlockStatementGroup().stream()
                            .map(this::visitSwitchBlockStatementGroup)
                            .reduce(new LinkedList<>(), (r, e) -> {
                                r.addAll(e);
                                return r;
                            });

            List caseStatementList = new LinkedList<>();
            List defaultStatementList = new LinkedList<>();

            for (Statement e : statementList) {
                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("a switch must only have one default branch", defaultStatementList.get(0));
            }

            if (defaultStatementListSize > 0 && last(statementList) instanceof CaseStatement) {
                throw createParsingFailedException("a default branch must only appear as the last branch of a switch", defaultStatementList.get(0));
            }

            return configureAST(
                    new SwitchStatement(
                            this.visitExpressionInPar(ctx.expressionInPar()),
                            caseStatementList,
                            defaultStatementListSize == 0 ? EmptyStatement.INSTANCE : defaultStatementList.get(0)
                    ),
                    ctx);
        } finally {
            switchExpressionRuleContextStack.pop();
            visitingSwitchStatementCount -= 1;
        }
    }

    @Override
    public List visitSwitchBlockStatementGroup(final SwitchBlockStatementGroupContext ctx) {
        int labelCount = ctx.switchLabel().size();
        List firstLabelHolder = new ArrayList<>(1);

        return (List) ctx.switchLabel().stream()
                .map(e -> (Object) this.visitSwitchLabel(e))
                .reduce(new ArrayList(4), (r, e) -> {
                    Statement statement;
                    List statementList = (List) r;
                    Tuple2 tuple = (Tuple2) e;
                    switch (tuple.getV1().getType()) {
                      case CASE:
                        if (!asBoolean(statementList)) {
                            firstLabelHolder.add(tuple.getV1());
                        }
                        statement = new CaseStatement(
                                tuple.getV2(),
                                // check whether processing the last label; if yes, block statement should be attached
                                (statementList.size() == labelCount - 1) ? this.visitBlockStatements(ctx.blockStatements()) : EmptyStatement.INSTANCE
                        );
                        statementList.add(configureAST(statement, firstLabelHolder.get(0)));
                        break;
                      case DEFAULT:
                        statement = this.visitBlockStatements(ctx.blockStatements());
                        statement.putNodeMetaData(IS_SWITCH_DEFAULT, Boolean.TRUE);
                        statementList.add(statement);
                        break;
                    }
                    return statementList;
                });
    }

    @Override
    public Tuple2 visitSwitchLabel(final SwitchLabelContext ctx) {
        if (asBoolean(ctx.CASE())) {
            return tuple(ctx.CASE().getSymbol(), (Expression) this.visit(ctx.expression()));
        } else if (asBoolean(ctx.DEFAULT())) {
            return tuple(ctx.DEFAULT().getSymbol(), EmptyExpression.INSTANCE);
        }

        throw createParsingFailedException("Unsupported switch label: " + ctx.getText(), ctx);
    }

    @Override
    public SynchronizedStatement visitSynchronizedStmtAlt(final SynchronizedStmtAltContext ctx) {
        return configureAST(
                new SynchronizedStatement(this.visitExpressionInPar(ctx.expressionInPar()), this.visitBlock(ctx.block())),
                ctx);
    }

    @Override
    public ReturnStatement visitReturnStmtAlt(final ReturnStmtAltContext ctx) {
        if (switchExpressionRuleContextStack.peek() instanceof SwitchExpressionContext) {
            throw createParsingFailedException("switch expression does not support `return`", ctx);
        }

        return configureAST(new ReturnStatement(asBoolean(ctx.expression())
                        ? (Expression) this.visit(ctx.expression())
                        : ConstantExpression.EMPTY_EXPRESSION),
                ctx);
    }

    @Override
    public ThrowStatement visitThrowStmtAlt(final ThrowStmtAltContext ctx) {
        return configureAST(
                new ThrowStatement((Expression) this.visit(ctx.expression())),
                ctx);
    }

    @Override
    public Statement visitLabeledStmtAlt(final LabeledStmtAltContext ctx) {
        Statement statement = (Statement) this.visit(ctx.statement());

        statement.addStatementLabel(this.visitIdentifier(ctx.identifier()));

        return statement;
    }

    @Override
    public BreakStatement visitBreakStatement(final BreakStatementContext ctx) {
        if (visitingLoopStatementCount == 0 && visitingSwitchStatementCount == 0) {
            throw createParsingFailedException("break statement is only allowed inside loops or switches", ctx);
        }

        if (switchExpressionRuleContextStack.peek() instanceof SwitchExpressionContext) {
            throw createParsingFailedException("switch expression does not support `break`", ctx);
        }

        String label = asBoolean(ctx.identifier())
                ? this.visitIdentifier(ctx.identifier())
                : null;

        return configureAST(new BreakStatement(label), ctx);
    }

    @Override
    public ReturnStatement visitYieldStatement(final YieldStatementContext ctx) {
        ReturnStatement returnStatement = (ReturnStatement) returnS((Expression) this.visit(ctx.expression()));
        returnStatement.putNodeMetaData(IS_YIELD_STATEMENT, Boolean.TRUE);
        return configureAST(returnStatement, ctx);
    }

    @Override
    public ReturnStatement visitYieldStmtAlt(final 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);
        }

        if (switchExpressionRuleContextStack.peek() instanceof SwitchExpressionContext) {
            throw createParsingFailedException("switch expression does not support `continue`", ctx);
        }

        String label = asBoolean(ctx.identifier())
                ? this.visitIdentifier(ctx.identifier())
                : null;

        return configureAST(new ContinueStatement(label), ctx);

    }

    @Override
    public Expression visitSwitchExprAlt(final SwitchExprAltContext ctx) {
        return configureAST(this.visitSwitchExpression(ctx.switchExpression()), ctx);
    }

    /**
     * 
     * switch(a) {
     *     case 0, 1  ->   'a';
     *     case 2     ->   'b';
     *     default    ->   'z';
     * }
     * 
* the above code will be transformed to: *
     * {->
     *     switch(a) {
     *         case 0:
     *         case 1:  return 'a';
     *         case 2:  return 'b';
     *         default: return 'z';
     *     }
     * }()
     * 
* * @param ctx the parse tree * @return {@link MethodCallExpression} instance */ @Override public MethodCallExpression visitSwitchExpression(final SwitchExpressionContext ctx) { switchExpressionRuleContextStack.push(ctx); try { validateSwitchExpressionLabels(ctx); List, Boolean, Boolean>> statementInfoList = ctx.switchBlockStatementExpressionGroup().stream() .map(e -> this.visitSwitchBlockStatementExpressionGroup(e)) .collect(Collectors.toList()); if (statementInfoList.isEmpty()) { throw createParsingFailedException("`case` or `default` branches are expected", ctx.LBRACE()); } Boolean isArrow = statementInfoList.get(0).getV2(); if (!isArrow && statementInfoList.stream().noneMatch(e -> { Boolean hasYieldOrThrowStatement = e.getV3(); return hasYieldOrThrowStatement; })) { throw createParsingFailedException("`yield` or `throw` is expected", ctx); } List statementList = statementInfoList.stream().map(e -> e.getV1()) .reduce(new LinkedList<>(), (r, e) -> { r.addAll(e); return r; }); List caseStatementList = new LinkedList<>(); List 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)); } String variableName = "__$$sev" + switchExpressionVariableSeq++; Statement declarationStatement = declS(localVarX(variableName), this.visitExpressionInPar(ctx.expressionInPar())); SwitchStatement switchStatement = configureAST( new SwitchStatement( varX(variableName), caseStatementList, defaultStatementListSize == 0 ? EmptyStatement.INSTANCE : defaultStatementList.get(0) ), ctx); MethodCallExpression callClosure = callX( configureAST( closureX(null, createBlockStatement(declarationStatement, switchStatement)), ctx ), CALL_STR); callClosure.setImplicitThis(false); return configureAST(callClosure, ctx); } finally { switchExpressionRuleContextStack.pop(); } } private int switchExpressionVariableSeq; @Override public Tuple3, Boolean, Boolean> visitSwitchBlockStatementExpressionGroup(SwitchBlockStatementExpressionGroupContext ctx) { int labelCnt = ctx.switchExpressionLabel().size(); List firstLabelHolder = new ArrayList<>(1); final int[] arrowCntHolder = new int[1]; boolean[] isArrowHolder = new boolean[1]; boolean[] hasResultStmtHolder = new boolean[1]; List result = (List) ctx.switchExpressionLabel().stream() .map(e -> (Object) this.visitSwitchExpressionLabel(e)) .reduce(new ArrayList(4), (r, e) -> { List statementList = (List) r; Tuple3, Integer> tuple = (Tuple3, Integer>) e; boolean isArrow = ARROW == tuple.getV3(); isArrowHolder[0] = isArrow; if (isArrow) { 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 statements = codeBlock.getStatements(); int statementsCnt = statements.size(); if (0 == statementsCnt) { throw createParsingFailedException("`yield` is expected", ctx.blockStatements()); } if (isArrow && statementsCnt > 1) { throw createParsingFailedException("Expect only 1 statement, but " + statementsCnt + " statements found", ctx.blockStatements()); } if (!isArrow) { boolean[] hasYieldHolder = new boolean[1]; boolean[] hasThrowHolder = new boolean[1]; codeBlock.visit(new CodeVisitorSupport() { @Override public void visitReturnStatement(ReturnStatement statement) { if (isTrue(statement, IS_YIELD_STATEMENT)) { hasYieldHolder[0] = true; return; } super.visitReturnStatement(statement); } @Override public void visitThrowStatement(ThrowStatement statement) { hasThrowHolder[0] = true; } }); if (hasYieldHolder[0] || hasThrowHolder[0]) { hasResultStmtHolder[0] = true; } } Statement exprOrBlockStatement = statements.get(0); if (exprOrBlockStatement instanceof BlockStatement) { BlockStatement blockStatement = (BlockStatement) exprOrBlockStatement; List branchStatementList = blockStatement.getStatements(); if (1 == branchStatementList.size()) { exprOrBlockStatement = branchStatementList.get(0); } } if (!(exprOrBlockStatement instanceof ReturnStatement || exprOrBlockStatement instanceof ThrowStatement)) { if (isArrow) { MethodCallExpression callClosure = callX( configureAST( closureX(null, exprOrBlockStatement), exprOrBlockStatement ), CALL_STR); callClosure.setImplicitThis(false); Expression resultExpr = exprOrBlockStatement instanceof ExpressionStatement ? ((ExpressionStatement) exprOrBlockStatement).getExpression() : callClosure; codeBlock = configureAST( createBlockStatement(configureAST( returnS(resultExpr), exprOrBlockStatement )), exprOrBlockStatement ); } } switch (tuple.getV1().getType()) { case CASE: if (!asBoolean(statementList)) { firstLabelHolder.add(tuple.getV1()); } for (int i = 0, n = tuple.getV2().size(); i < n; i += 1) { 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, Boolean.TRUE); statementList.add(codeBlock); break; } return statementList; }); return tuple(result, isArrowHolder[0], hasResultStmtHolder[0]); } private void validateSwitchExpressionLabels(SwitchExpressionContext ctx) { Map> acMap = ctx.switchBlockStatementExpressionGroup().stream() .flatMap(e -> e.switchExpressionLabel().stream()) .collect(Collectors.groupingBy(e -> e.ac.getText())); if (acMap.size() > 1) { List 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, 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 public ClassNode visitTypeDeclaration(final TypeDeclarationContext ctx) { if (asBoolean(ctx.classDeclaration())) { // e.g. class A {} ctx.classDeclaration().putNodeMetaData(TYPE_DECLARATION_MODIFIERS, this.visitClassOrInterfaceModifiersOpt(ctx.classOrInterfaceModifiersOpt())); return configureAST(this.visitClassDeclaration(ctx.classDeclaration()), ctx); } throw createParsingFailedException("Unsupported type declaration: " + ctx.getText(), ctx); } @Override public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { String packageName = Optional.ofNullable(this.moduleNode.getPackageName()).orElse(""); String className = this.visitIdentifier(ctx.identifier()); if ("var".equals(className)) { throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier()); } boolean isAnnotation = asBoolean(ctx.AT()); if (isAnnotation) { if (asBoolean(ctx.typeParameters())) { throw createParsingFailedException("annotation declaration cannot have type parameters", ctx.typeParameters()); } if (asBoolean(ctx.EXTENDS())) { throw createParsingFailedException("No extends clause allowed for annotation declaration", ctx.EXTENDS()); } if (asBoolean(ctx.IMPLEMENTS())) { throw createParsingFailedException("No implements clause allowed for annotation declaration", ctx.IMPLEMENTS()); } } boolean isEnum = asBoolean(ctx.ENUM()); if (isEnum) { if (asBoolean(ctx.typeParameters())) { throw createParsingFailedException("enum declaration cannot have type parameters", ctx.typeParameters()); } if (asBoolean(ctx.EXTENDS())) { throw createParsingFailedException("No extends clause allowed for enum declaration", ctx.EXTENDS()); } } boolean isInterface = (asBoolean(ctx.INTERFACE()) && !isAnnotation); if (isInterface) { if (asBoolean(ctx.IMPLEMENTS())) { throw createParsingFailedException("No implements clause allowed for interface declaration", ctx.IMPLEMENTS()); } } ModifierManager modifierManager = new ModifierManager(this, ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS)); Optional finalModifier = modifierManager.get(FINAL); Optional sealedModifier = modifierManager.get(SEALED); Optional nonSealedModifier = modifierManager.get(NON_SEALED); boolean isFinal = finalModifier.isPresent(); boolean isSealed = sealedModifier.isPresent(); boolean isNonSealed = nonSealedModifier.isPresent(); boolean isRecord = asBoolean(ctx.RECORD()); boolean hasRecordHeader = asBoolean(ctx.formalParameters()); if (isRecord) { if (!hasRecordHeader) { throw createParsingFailedException("header declaration of record is expected", ctx.identifier()); } if (asBoolean(ctx.EXTENDS())) { throw createParsingFailedException("No extends clause allowed for record declaration", ctx.EXTENDS()); } if (isSealed) { throw createParsingFailedException("`sealed` is not allowed for record declaration", sealedModifier.get()); } if (isNonSealed) { throw createParsingFailedException("`non-sealed` is not allowed for record declaration", nonSealedModifier.get()); } } else { if (hasRecordHeader) { throw createParsingFailedException("header declaration is only allowed for record declaration", ctx.formalParameters()); } } if (isSealed && isNonSealed) { throw createParsingFailedException("type cannot be defined with both `sealed` and `non-sealed`", nonSealedModifier.get()); } if (isFinal && (isSealed || isNonSealed)) { throw createParsingFailedException("type cannot be defined with both " + (isSealed ? "`sealed`" : "`non-sealed`") + " and `final`", finalModifier.get()); } if ((isAnnotation || isEnum) && (isSealed || isNonSealed)) { ModifierNode mn = isSealed ? sealedModifier.get() : nonSealedModifier.get(); throw createParsingFailedException("modifier `" + mn.getText() + "` is not allowed for " + (isEnum ? "enum" : "annotation definition"), mn); } boolean hasPermits = asBoolean(ctx.PERMITS()); if (!isSealed && hasPermits) { throw createParsingFailedException("only sealed type declarations should have `permits` clause", ctx); } int modifiers = modifierManager.getClassModifiersOpValue(); boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0); modifiers &= ~Opcodes.ACC_SYNTHETIC; ClassNode classNode, outerClass = this.classNodeStack.peek(); if (isEnum) { classNode = EnumHelper.makeEnumNode( asBoolean(outerClass) ? className : packageName + className, modifiers, null, outerClass ); } else if (asBoolean(outerClass)) { if (outerClass.isInterface()) modifiers |= Opcodes.ACC_STATIC; classNode = new InnerClassNode( outerClass, outerClass.getName() + "$" + className, modifiers, ClassHelper.OBJECT_TYPE.getPlainNodeReference() ); } else { classNode = new ClassNode( packageName + className, modifiers, ClassHelper.OBJECT_TYPE.getPlainNodeReference() ); } configureAST(classNode, ctx); classNode.setSyntheticPublic(syntheticPublic); classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters())); boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultOrPrivateMethods(ctx)); if (isSealed) { AnnotationNode sealedAnnotationNode = makeAnnotationNode(Sealed.class); if (asBoolean(ctx.ps)) { ListExpression permittedSubclassesListExpression = listX(Arrays.stream(this.visitTypeList(ctx.ps)) .map(ClassExpression::new) .collect(Collectors.toList())); sealedAnnotationNode.setMember("permittedSubclasses", permittedSubclassesListExpression); configureAST(sealedAnnotationNode, ctx.PERMITS()); sealedAnnotationNode.setNodeMetaData("permits", Boolean.TRUE); } classNode.addAnnotation(sealedAnnotationNode); } else if (isNonSealed) { classNode.addAnnotation(makeAnnotationNode(NonSealed.class)); } if (asBoolean(ctx.TRAIT())) { classNode.addAnnotation(makeAnnotationNode(Trait.class)); } classNode.addAnnotations(modifierManager.getAnnotations()); if (isRecord && classNode.getAnnotations().stream().noneMatch(a -> a.getClassNode().getName().equals(RECORD_TYPE_NAME))) { classNode.addAnnotation(new AnnotationNode(ClassHelper.makeWithoutCaching(RECORD_TYPE_NAME))); // TODO: makeAnnotationNode(RecordType.class) } if (isInterfaceWithDefaultMethods) { classNode.putNodeMetaData(IS_INTERFACE_WITH_DEFAULT_METHODS, Boolean.TRUE); } classNode.putNodeMetaData(CLASS_NAME, className); if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT())) { if (asBoolean(ctx.scs)) { ClassNode[] scs = this.visitTypeList(ctx.scs); if (scs.length > 1) { throw createParsingFailedException("Cannot extend multiple classes", ctx.EXTENDS()); } classNode.setSuperClass(scs[0]); } classNode.setInterfaces(this.visitTypeList(ctx.is)); this.checkUsingGenerics(classNode); } else if (isInterface) { classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT); classNode.setInterfaces(this.visitTypeList(ctx.scs)); this.checkUsingGenerics(classNode); this.hackMixins(classNode); } else if (isEnum || isRecord) { classNode.setInterfaces(this.visitTypeList(ctx.is)); this.checkUsingGenerics(classNode); if (isRecord) { this.transformRecordHeaderToProperties(ctx, classNode); } } else if (isAnnotation) { 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); } this.classNodeStack.push(classNode); ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode); this.visitClassBody(ctx.classBody()); if (isRecord) { classNode.getFields().stream().filter(f -> !isTrue(f, IS_RECORD_GENERATED) && !f.isStatic()).findFirst() .ifPresent(fn -> this.createParsingFailedException("Instance field is not allowed in `record`", fn)); } this.classNodeStack.pop(); // The first element in classNodeList determines what GCL#parseClass for // example will return. So we have to ensure it won't be an inner class. if (outerClass == null) { this.addToClassNodeList(classNode); } this.groovydocManager.handle(classNode, ctx); return classNode; } private void addToClassNodeList(final ClassNode classNode) { this.classNodeList.add(classNode); // GROOVY-11117: outer class first classNode.getInnerClasses().forEachRemaining(this::addToClassNodeList); } private void checkUsingGenerics(final ClassNode classNode) { if (!classNode.isUsingGenerics()) { if (!classNode.isEnum() && classNode.getSuperClass().isUsingGenerics()) { classNode.setUsingGenerics(true); } else if (classNode.getInterfaces() != null) { for (ClassNode interfaceNode : classNode.getInterfaces()) { if (interfaceNode.isUsingGenerics()) { classNode.setUsingGenerics(true); break; } } } } } private void transformRecordHeaderToProperties(final ClassDeclarationContext ctx, final ClassNode classNode) { Parameter[] parameters = this.visitFormalParameters(ctx.formalParameters()); classNode.putNodeMetaData(RECORD_HEADER, parameters); final int n = parameters.length; for (int i = 0; i < n; i += 1) { Parameter parameter = parameters[i]; FormalParameterContext parameterCtx = parameter.getNodeMetaData(PARAMETER_CONTEXT); ModifierManager parameterModifierManager = parameter.getNodeMetaData(PARAMETER_MODIFIER_MANAGER); PropertyNode propertyNode = declareProperty(parameterCtx, parameterModifierManager, parameter.getType(), classNode, i, parameter, parameter.getName(), parameter.getModifiers() | Opcodes.ACC_FINAL, parameter.getInitialExpression()); propertyNode.getField().putNodeMetaData(IS_RECORD_GENERATED, Boolean.TRUE); } } private boolean containsDefaultOrPrivateMethods(final ClassDeclarationContext ctx) { List methodDeclarationContextList = (List) ctx.classBody().classBodyDeclaration().stream() .map(ClassBodyDeclarationContext::memberDeclaration) .filter(Objects::nonNull) .map(e -> (Object) e.methodDeclaration()) .filter(Objects::nonNull).reduce(new LinkedList(), (r, e) -> { MethodDeclarationContext methodDeclarationContext = (MethodDeclarationContext) e; if (createModifierManager(methodDeclarationContext).containsAny(DEFAULT, PRIVATE)) { ((List) r).add(methodDeclarationContext); } return r; }); return !methodDeclarationContextList.isEmpty(); } @Override public Void visitClassBody(final 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().forEach(e -> { e.putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode); this.visitClassBodyDeclaration(e); }); return null; } @Override public List visitEnumConstants(final 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(final 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)); enumConstant.addAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); groovydocManager.handle(enumConstant, ctx); return configureAST(enumConstant, ctx); } private Expression createEnumConstantInitExpression(final ArgumentsContext ctx, final InnerClassNode anonymousInnerClassNode) { if (!asBoolean(ctx) && !asBoolean(anonymousInnerClassNode)) { return null; } TupleExpression argumentListExpression = (TupleExpression) this.visitArguments(ctx); List 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 mapEntryExpressionList = ((NamedArgumentListExpression) expression).getMapEntryExpressions(); ListExpression listExpression = new ListExpression( mapEntryExpressionList.stream() .map(e -> (Expression) e) .collect(Collectors.toList())); if (asBoolean(anonymousInnerClassNode)) { listExpression.addExpression( configureAST( new ClassExpression(anonymousInnerClassNode), anonymousInnerClassNode)); } if (mapEntryExpressionList.size() > 1) { listExpression.setWrapped(true); } return configureAST(listExpression, ctx); } if (!asBoolean(anonymousInnerClassNode)) { if (expression instanceof ListExpression) { ListExpression listExpression = new ListExpression(); listExpression.addExpression(expression); return configureAST(listExpression, ctx); } return expression; } ListExpression listExpression = new ListExpression(); if (expression instanceof ListExpression) { ((ListExpression) expression).getExpressions().forEach(listExpression::addExpression); } else { listExpression.addExpression(expression); } listExpression.addExpression( configureAST( new ClassExpression(anonymousInnerClassNode), anonymousInnerClassNode)); return configureAST(listExpression, ctx); } ListExpression listExpression = new ListExpression(expressions); if (asBoolean(anonymousInnerClassNode)) { listExpression.addExpression( configureAST( new ClassExpression(anonymousInnerClassNode), anonymousInnerClassNode)); } if (asBoolean(ctx)) { listExpression.setWrapped(true); } return asBoolean(ctx) ? configureAST(listExpression, ctx) : configureAST(listExpression, anonymousInnerClassNode); } @Override public Void visitClassBodyDeclaration(final ClassBodyDeclarationContext ctx) { ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE); 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(configureAST(this.createBlockStatement(statement), statement)); } } return null; } @Override public Void visitMemberDeclaration(final 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.compactConstructorDeclaration())) { ctx.compactConstructorDeclaration().putNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS, this.visitModifiersOpt(ctx.modifiersOpt())); ctx.compactConstructorDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode); this.visitCompactConstructorDeclaration(ctx.compactConstructorDeclaration()); } 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(final TypeParametersContext ctx) { if (!asBoolean(ctx)) { return null; } return ctx.typeParameter().stream() .map(this::visitTypeParameter) .toArray(GenericsType[]::new); } @Override public GenericsType visitTypeParameter(final TypeParameterContext ctx) { ClassNode baseType = configureAST(ClassHelper.make(this.visitClassName(ctx.className())), ctx); baseType.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); GenericsType genericsType = new GenericsType(baseType, this.visitTypeBound(ctx.typeBound()), null); return configureAST(genericsType, ctx); } @Override public ClassNode[] visitTypeBound(final TypeBoundContext ctx) { if (!asBoolean(ctx)) { return null; } return ctx.type().stream() .map(this::visitType) .toArray(ClassNode[]::new); } @Override public Void visitFieldDeclaration(final 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(final Statement statement) { if (!(statement instanceof BlockStatement)) { // method code must be a BlockStatement return null; } BlockStatement blockStatement = (BlockStatement) statement; List statementList = blockStatement.getStatements(); for (int i = 0, n = statementList.size(); i < n; i += 1) { 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; } private ModifierManager createModifierManager(final MethodDeclarationContext ctx) { List modifierNodeList = Collections.emptyList(); if (asBoolean(ctx.modifiersOpt())) { modifierNodeList = this.visitModifiersOpt(ctx.modifiersOpt()); } return new ModifierManager(this, modifierNodeList); } private void validateParametersOfMethodDeclaration(final Parameter[] parameters, final ClassNode classNode) { if (!classNode.isInterface()) { return; } for (Parameter parameter : parameters) { if (parameter.hasInitialExpression()) { throw createParsingFailedException("Cannot specify default value for method parameter '" + parameter.getName() + " = " + parameter.getInitialExpression().getText() + "' inside an interface", parameter); } } } @Override public MethodNode visitCompactConstructorDeclaration(final CompactConstructorDeclarationContext ctx) { ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE); if (classNode.getAnnotations().stream().noneMatch(a -> a.getClassNode().getName().equals(RECORD_TYPE_NAME))) { createParsingFailedException("Only record can have compact constructor", ctx); } if (new ModifierManager(this, ctx.getNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS)).containsAny(VAR)) { throw createParsingFailedException("var cannot be used for compact constructor declaration", ctx); } String methodName = this.visitMethodName(ctx.methodName()); String className = classNode.getNodeMetaData(CLASS_NAME); if (!methodName.equals(className)) { createParsingFailedException("Compact constructor should have the same name as record: " + className, ctx.methodName()); } Parameter[] header = classNode.getNodeMetaData(RECORD_HEADER); Statement code = this.visitMethodBody(ctx.methodBody()); code.visit(new CodeVisitorSupport() { @Override public void visitPropertyExpression(final PropertyExpression expression) { String receiverText = expression.getObjectExpression().getText(); String propertyName = expression.getPropertyAsString(); if (THIS_STR.equals(receiverText) && Arrays.stream(header).anyMatch(p -> p.getName().equals(propertyName))) { createParsingFailedException("Cannot assign a value to final variable '" + propertyName + "'", expression.getProperty()); } super.visitPropertyExpression(expression); } }); List annos = classNode.getAnnotations(ClassHelper.make(TupleConstructor.class)); AnnotationNode tupleConstructor = annos.isEmpty() ? makeAnnotationNode(TupleConstructor.class) : annos.get(0); tupleConstructor.setMember("pre", closureX(code)); if (annos.isEmpty()) { classNode.addAnnotation(tupleConstructor); } return null; } @Override public MethodNode visitMethodDeclaration(final MethodDeclarationContext ctx) { ModifierManager modifierManager = createModifierManager(ctx); if (modifierManager.containsAny(VAR)) { throw createParsingFailedException("var cannot be used for method declarations", ctx); } 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 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)) { validateParametersOfMethodDeclaration(parameters, classNode); methodNode = createConstructorOrMethodNodeForClass(ctx, modifierManager, methodName, returnType, parameters, exceptions, code, classNode); } else { // script method declaration methodNode = createScriptMethodNode(modifierManager, methodName, returnType, parameters, exceptions, code); } anonymousInnerClassList.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.containsAny(STATIC)) { for (Parameter parameter : methodNode.getParameters()) { parameter.setInStaticContext(true); } methodNode.getVariableScope().setInStaticContext(true); } configureAST(methodNode, ctx); validateMethodDeclaration(ctx, methodNode, modifierManager, classNode); groovydocManager.handle(methodNode, ctx); return methodNode; } private void validateMethodDeclaration(final MethodDeclarationContext ctx, final MethodNode methodNode, final ModifierManager modifierManager, final ClassNode classNode) { if (1 == ctx.t || 2 == ctx.t || 3 == ctx.t) { // 1: normal method declaration; 2: abstract method declaration; 3: normal method declaration OR abstract method declaration if (!(asBoolean(ctx.modifiersOpt().modifiers()) || asBoolean(ctx.returnType()))) { throw createParsingFailedException("Modifiers or return type is required", ctx); } } if (1 == ctx.t) { if (!asBoolean(ctx.methodBody())) { throw createParsingFailedException("Method body is required", ctx); } } if (2 == ctx.t) { if (asBoolean(ctx.methodBody())) { throw createParsingFailedException("Abstract method should not have method body", ctx); } } boolean isAbstractMethod = methodNode.isAbstract(); boolean hasMethodBody = asBoolean(methodNode.getCode()) && !(methodNode.getCode() instanceof ExpressionStatement); if (9 == ctx.ct) { // script if (isAbstractMethod || !hasMethodBody) { // method should not be declared abstract in the script throw createParsingFailedException("You cannot define " + (isAbstractMethod ? "an abstract" : "a") + " 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 (4 == ctx.ct) { // trait if (isAbstractMethod && hasMethodBody) { throw createParsingFailedException("Abstract method should not have method body", ctx); } } if (3 == ctx.ct) { // annotation if (hasMethodBody) { throw createParsingFailedException("Annotation type element should not have body", ctx); } } 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 a body. Try adding a method body, or declare it abstract", methodNode); } boolean isInterfaceOrAbstractClass = asBoolean(classNode) && classNode.isAbstract() && !classNode.isAnnotationDefinition(); if (isInterfaceOrAbstractClass && !modifierManager.containsAny(DEFAULT, PRIVATE) && isAbstractMethod && hasMethodBody) { throw createParsingFailedException("You defined an abstract method[" + methodNode.getName() + "] with a body. Try removing the method body" + (classNode.isInterface() ? ", or declare it default or private" : ""), methodNode); } } modifierManager.validate(methodNode); if (methodNode instanceof ConstructorNode) { modifierManager.validate((ConstructorNode) methodNode); } } private MethodNode createScriptMethodNode(final ModifierManager modifierManager, final String methodName, final ClassNode returnType, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code) { MethodNode methodNode = new MethodNode( methodName, modifierManager.containsAny(PRIVATE) ? Opcodes.ACC_PRIVATE : Opcodes.ACC_PUBLIC, returnType, parameters, exceptions, code ); modifierManager.processMethodNode(methodNode); return methodNode; } private MethodNode createConstructorOrMethodNodeForClass(final MethodDeclarationContext ctx, final ModifierManager modifierManager, final String methodName, final ClassNode returnType, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code, final ClassNode classNode) { MethodNode methodNode; String className = classNode.getNodeMetaData(CLASS_NAME); int modifiers = modifierManager.getClassMemberModifiersOpValue(); boolean hasReturnType = asBoolean(ctx.returnType()); boolean hasMethodBody = asBoolean(ctx.methodBody()); if (!hasReturnType && hasMethodBody && methodName.equals(className)) { methodNode = createConstructorNodeForClass(methodName, parameters, exceptions, code, classNode, modifiers); } else { if (!hasReturnType && hasMethodBody && (0 == modifierManager.getModifierCount())) { throw createParsingFailedException("Invalid method declaration: " + methodName, ctx); } methodNode = createMethodNodeForClass(ctx, modifierManager, methodName, returnType, parameters, exceptions, code, classNode, modifiers); } modifierManager.attachAnnotations(methodNode); return methodNode; } private MethodNode createMethodNodeForClass(final MethodDeclarationContext ctx, final ModifierManager modifierManager, final String methodName, final ClassNode returnType, final Parameter[] parameters, final ClassNode[] exceptions, Statement code, final ClassNode classNode, int modifiers) { if (asBoolean(ctx.elementValue())) { // the code of annotation method code = configureAST( new ExpressionStatement( this.visitElementValue(ctx.elementValue())), ctx.elementValue()); } modifiers |= !modifierManager.containsAny(STATIC) && classNode.isInterface() && !(isTrue(classNode, IS_INTERFACE_WITH_DEFAULT_METHODS) && modifierManager.containsAny(DEFAULT, PRIVATE)) ? Opcodes.ACC_ABSTRACT : 0; MethodNode methodNode = new MethodNode(methodName, modifiers, returnType, parameters, exceptions, code); classNode.addMethod(methodNode); methodNode.setAnnotationDefault(asBoolean(ctx.elementValue())); return methodNode; } private ConstructorNode createConstructorNodeForClass(final String methodName, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code, final ClassNode classNode, final int modifiers) { ConstructorCallExpression thisOrSuperConstructorCallExpression = this.checkThisAndSuperConstructorCall(code); if (asBoolean(thisOrSuperConstructorCallExpression)) { throw createParsingFailedException(thisOrSuperConstructorCallExpression.getText() + " should be the first statement in the constructor[" + methodName + "]", thisOrSuperConstructorCallExpression); } return classNode.addConstructor( modifiers, parameters, exceptions, code); } @Override public String visitMethodName(final 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(final ReturnTypeContext ctx) { if (!asBoolean(ctx)) { return ClassHelper.dynamicType(); } if (asBoolean(ctx.type())) { return this.visitType(ctx.type()); } if (asBoolean(ctx.VOID())) { if (ctx.ct == 3) { // annotation throw createParsingFailedException("annotation method cannot have void return type", ctx); } return configureAST(ClassHelper.VOID_TYPE.getPlainNodeReference(false), ctx.VOID()); } throw createParsingFailedException("Unsupported return type: " + ctx.getText(), ctx); } @Override public Statement visitMethodBody(final MethodBodyContext ctx) { if (!asBoolean(ctx)) { return null; } return configureAST(this.visitBlock(ctx.block()), ctx); } @Override public DeclarationListStatement visitLocalVariableDeclaration(final LocalVariableDeclarationContext ctx) { return configureAST(this.visitVariableDeclaration(ctx.variableDeclaration()), ctx); } private DeclarationListStatement createMultiAssignmentDeclarationListStatement(final VariableDeclarationContext ctx, final ModifierManager modifierManager) { List elist = this.visitTypeNamePairs(ctx.typeNamePairs()); for (Expression e : elist) modifierManager.processVariableExpression((VariableExpression) e); DeclarationExpression de = new DeclarationExpression( configureAST(new TupleExpression(elist), ctx.typeNamePairs()), createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN), visitVariableInitializer(ctx.variableInitializer()) ); configureAST(modifierManager.attachAnnotations(de), ctx); return configureAST(new DeclarationListStatement(de), ctx); } @Override public DeclarationListStatement visitVariableDeclaration(final VariableDeclarationContext ctx) { ModifierManager modifierManager = new ModifierManager( this, asBoolean(ctx.modifiers()) ? this.visitModifiers(ctx.modifiers()) : Collections.emptyList() ); if (asBoolean(ctx.typeNamePairs())) { // e.g. def (int a, int b) = [1, 2] return this.createMultiAssignmentDeclarationListStatement(ctx, modifierManager); } ClassNode variableType = this.visitType(ctx.type()); ctx.variableDeclarators().putNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE, variableType); List 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)) { return createFieldDeclarationListStatement(ctx, modifierManager, variableType, declarationExpressionList, classNode); } int size = declarationExpressionList.size(); if (size > 0) { for (DeclarationExpression e : declarationExpressionList) { modifierManager.processVariableExpression(e.getVariableExpression()); modifierManager.attachAnnotations(e); } DeclarationExpression declarationExpression = declarationExpressionList.get(0); if (size == 1) { configureAST(declarationExpression, ctx); } else { // adjust start of first declaration declarationExpression.setLineNumber(ctx.getStart().getLine()); declarationExpression.setColumnNumber(ctx.getStart().getCharPositionInLine() + 1); } } return configureAST(new DeclarationListStatement(declarationExpressionList), ctx); } private DeclarationListStatement createFieldDeclarationListStatement(final VariableDeclarationContext ctx, final ModifierManager modifierManager, final ClassNode variableType, final List declarationExpressionList, final ClassNode classNode) { for (int i = 0, n = declarationExpressionList.size(); i < n; i += 1) { DeclarationExpression declarationExpression = declarationExpressionList.get(i); VariableExpression variableExpression = (VariableExpression) declarationExpression.getLeftExpression(); String fieldName = variableExpression.getName(); int modifiers = modifierManager.getClassMemberModifiersOpValue(); Expression initialValue = declarationExpression.getRightExpression() instanceof EmptyExpression ? null : declarationExpression.getRightExpression(); Object defaultValue = findDefaultValueByType(variableType); if (classNode.isInterface()) { if (!asBoolean(initialValue)) { initialValue = !asBoolean(defaultValue) ? null : new ConstantExpression(defaultValue, true); } modifiers |= Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL; } if (isFieldDeclaration(modifierManager, classNode)) { declareField(ctx, modifierManager, variableType, classNode, i, variableExpression, fieldName, modifiers, initialValue); } else { declareProperty(ctx, modifierManager, variableType, classNode, i, variableExpression, fieldName, modifiers, initialValue); } } return null; } private static class PropertyExpander extends Verifier { private PropertyExpander(final ClassNode cNode) { setClassNode(cNode); } @Override protected Statement createSetterBlock(final PropertyNode propertyNode, final FieldNode field) { return stmt(assignX(varX(field), varX(VALUE_STR, field.getType()))); } @Override protected Statement createGetterBlock(final PropertyNode propertyNode, final FieldNode field) { return stmt(varX(field)); } } private PropertyNode declareProperty(final GroovyParserRuleContext ctx, final ModifierManager modifierManager, final ClassNode variableType, final ClassNode classNode, final int i, final ASTNode startNode, final String fieldName, final int modifiers, final Expression initialValue) { PropertyNode propertyNode; FieldNode fieldNode = classNode.getDeclaredField(fieldName); if (fieldNode != null && !classNode.hasProperty(fieldName)) { if (fieldNode.hasInitialExpression() && initialValue != null) { throw createParsingFailedException("The split property definition named '" + fieldName + "' must not have an initial value for both the field and the property", ctx); } if (!fieldNode.getType().equals(variableType)) { throw createParsingFailedException("The split property definition named '" + fieldName + "' must not have different types for the field and the property", ctx); } classNode.getFields().remove(fieldNode); propertyNode = new PropertyNode(fieldNode, modifiers | Opcodes.ACC_PUBLIC, null, null); classNode.addProperty(propertyNode); if (initialValue != null) { fieldNode.setInitialValueExpression(initialValue); } modifierManager.attachAnnotations(propertyNode); propertyNode.addAnnotation(makeAnnotationNode(CompileStatic.class)); // expand properties early so AST transforms will be handled correctly PropertyExpander expander = new PropertyExpander(classNode); expander.visitProperty(propertyNode); } else { propertyNode = new PropertyNode(fieldName, modifiers | Opcodes.ACC_PUBLIC, variableType, classNode, initialValue, null, null); classNode.addProperty(propertyNode); fieldNode = propertyNode.getField(); fieldNode.setModifiers(modifiers & ~Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE); fieldNode.setSynthetic(!classNode.isInterface()); modifierManager.attachAnnotations(fieldNode); modifierManager.attachAnnotations(propertyNode); if (i == 0) { configureAST(fieldNode, ctx, initialValue); } else { configureAST(fieldNode, startNode, initialValue); } } groovydocManager.handle(fieldNode, ctx); groovydocManager.handle(propertyNode, ctx); if (i == 0) { configureAST(propertyNode, ctx, initialValue); } else { configureAST(propertyNode, startNode, initialValue); } return propertyNode; } private void declareField(final VariableDeclarationContext ctx, final ModifierManager modifierManager, final ClassNode variableType, final ClassNode classNode, final int i, final VariableExpression variableExpression, final String fieldName, final int modifiers, final Expression initialValue) { FieldNode fieldNode; PropertyNode propertyNode = classNode.getProperty(fieldName); if (propertyNode != null && propertyNode.getField().isSynthetic()) { if (propertyNode.hasInitialExpression() && initialValue != null) { throw createParsingFailedException("The split property definition named '" + fieldName + "' must not have an initial value for both the field and the property", ctx); } if (!propertyNode.getType().equals(variableType)) { throw createParsingFailedException("The split property definition named '" + fieldName + "' must not have different types for the field and the property", ctx); } classNode.getFields().remove(propertyNode.getField()); fieldNode = new FieldNode(fieldName, modifiers, variableType, classNode.redirect(), propertyNode.hasInitialExpression() ? propertyNode.getInitialExpression() : initialValue); propertyNode.setField(fieldNode); propertyNode.addAnnotation(makeAnnotationNode(CompileStatic.class)); classNode.addField(fieldNode); // expand properties early so AST transforms will be handled correctly PropertyExpander expander = new PropertyExpander(classNode); expander.visitProperty(propertyNode); } else { fieldNode = classNode.addField( fieldName, modifiers, variableType, initialValue); } modifierManager.attachAnnotations(fieldNode); groovydocManager.handle(fieldNode, ctx); if (i == 0) { configureAST(fieldNode, ctx, initialValue); } else { configureAST(fieldNode, variableExpression, initialValue); } } private boolean isFieldDeclaration(final ModifierManager modifierManager, final ClassNode classNode) { return classNode.isInterface() || modifierManager.containsVisibilityModifier(); } @Override public List visitTypeNamePairs(final TypeNamePairsContext ctx) { return ctx.typeNamePair().stream().map(this::visitTypeNamePair).collect(Collectors.toList()); } @Override public VariableExpression visitTypeNamePair(final TypeNamePairContext ctx) { return configureAST( new VariableExpression( this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(), this.visitType(ctx.type())), ctx); } @Override public List visitVariableDeclarators(final 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); }) .collect(Collectors.toList()); } @Override public DeclarationExpression visitVariableDeclarator(final 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 configureAST( new DeclarationExpression( configureAST( new VariableExpression( this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(), variableType ), ctx.variableDeclaratorId()), token, this.visitVariableInitializer(ctx.variableInitializer())), ctx); } @Override public Expression visitVariableInitializer(final VariableInitializerContext ctx) { if (!asBoolean(ctx)) { return EmptyExpression.INSTANCE; } return configureAST( this.visitEnhancedStatementExpression(ctx.enhancedStatementExpression()), ctx); } @Override public List visitVariableInitializers(final VariableInitializersContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } return ctx.variableInitializer().stream() .map(this::visitVariableInitializer) .collect(Collectors.toList()); } @Override public List visitArrayInitializer(final ArrayInitializerContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } try { visitingArrayInitializerCount += 1; return this.visitVariableInitializers(ctx.variableInitializers()); } finally { visitingArrayInitializerCount -= 1; } } @Override public Statement visitBlock(final BlockContext ctx) { if (!asBoolean(ctx)) { return this.createBlockStatement(); } return configureAST( this.visitBlockStatementsOpt(ctx.blockStatementsOpt()), ctx); } @Override public ExpressionStatement visitCommandExprAlt(final CommandExprAltContext ctx) { return configureAST(new ExpressionStatement(this.visitCommandExpression(ctx.commandExpression())), ctx); } @Override public Expression visitCommandExpression(final CommandExpressionContext ctx) { boolean hasArgumentList = asBoolean(ctx.enhancedArgumentListInPar()); boolean hasCommandArgument = asBoolean(ctx.commandArgument()); if ((hasArgumentList || hasCommandArgument) && visitingArrayInitializerCount > 0) { // To avoid ambiguities, command chain expression should not be used in array initializer // the old parser does not support either, so no breaking changes // SEE http://groovy.329449.n5.nabble.com/parrot-Command-expressions-in-array-initializer-tt5752273.html throw createParsingFailedException("Command chain expression can not be used in array initializer", ctx); } Expression baseExpr = (Expression) this.visit(ctx.expression()); if ((hasArgumentList || hasCommandArgument) && !isInsideParentheses(baseExpr) && baseExpr instanceof BinaryExpression && !"[".equals(((BinaryExpression) baseExpr).getOperation().getText())) { throw createParsingFailedException("Unexpected input: '" + getOriginalText(ctx.expression()) + "'", ctx.expression()); } MethodCallExpression methodCallExpression = null; if (hasArgumentList) { Expression arguments = this.visitEnhancedArgumentListInPar(ctx.enhancedArgumentListInPar()); if (baseExpr instanceof PropertyExpression) { // e.g. obj.a 1, 2 methodCallExpression = configureAST(this.createMethodCallExpression((PropertyExpression) baseExpr, arguments), ctx.expression(), arguments); } else if (baseExpr instanceof MethodCallExpression && !isInsideParentheses(baseExpr)) { // 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 (!isInsideParentheses(baseExpr) && (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 validateInvalidMethodDefinition(baseExpr, arguments); methodCallExpression = configureAST(this.createMethodCallExpression(baseExpr, arguments), ctx.expression(), arguments); } else { // e.g. a[x] b, new A() b, etc. methodCallExpression = configureAST(this.createCallMethodCallExpression(baseExpr, arguments), ctx.expression(), arguments); } methodCallExpression.putNodeMetaData(IS_COMMAND_EXPRESSION, Boolean.TRUE); if (!hasCommandArgument) { return methodCallExpression; } } if (hasCommandArgument) { baseExpr.putNodeMetaData(IS_COMMAND_EXPRESSION, Boolean.TRUE); } return configureAST( (Expression) ctx.commandArgument().stream() .map(e -> (Object) e) .reduce(methodCallExpression != null ? methodCallExpression : baseExpr, (r, e) -> { CommandArgumentContext commandArgumentContext = (CommandArgumentContext) e; commandArgumentContext.putNodeMetaData(CMD_EXPRESSION_BASE_EXPR, r); return this.visitCommandArgument(commandArgumentContext); } ), ctx); } /* Validate the following invalid cases: * 1) void m() {} * 2) String m() {} * Note: if the text of `VariableExpression` does not start with upper case character, e.g. task m() {} * ,it may be a command expression */ private void validateInvalidMethodDefinition(final Expression baseExpr, final Expression arguments) { if (baseExpr instanceof VariableExpression) { if (isBuiltInType(baseExpr) || Character.isUpperCase(baseExpr.getText().codePointAt(0))) { if (arguments instanceof ArgumentListExpression) { List expressionList = ((ArgumentListExpression) arguments).getExpressions(); if (1 == expressionList.size()) { final Expression expression = expressionList.get(0); if (expression instanceof MethodCallExpression) { MethodCallExpression mce = (MethodCallExpression) expression; final Expression methodCallArguments = mce.getArguments(); // check the method call tails with a closure if (methodCallArguments instanceof ArgumentListExpression) { List methodCallArgumentExpressionList = ((ArgumentListExpression) methodCallArguments).getExpressions(); final int argumentCnt = methodCallArgumentExpressionList.size(); if (argumentCnt > 0) { final Expression lastArgumentExpression = methodCallArgumentExpressionList.get(argumentCnt - 1); if (lastArgumentExpression instanceof ClosureExpression) { if (ClosureUtils.hasImplicitParameter(((ClosureExpression) lastArgumentExpression))) { throw createParsingFailedException( "Method definition not expected here", tuple(baseExpr.getLineNumber(), baseExpr.getColumnNumber()), tuple(expression.getLastLineNumber(), expression.getLastColumnNumber()) ); } } } } } } } } } } @Override public Expression visitCommandArgument(final 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.enhancedArgumentListInPar())) { // 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.visitEnhancedArgumentListInPar(ctx.enhancedArgumentListInPar()) ); methodCallExpression.setImplicitThis(false); return configureAST(methodCallExpression, ctx); } else if (asBoolean(ctx.pathElement())) { // e.g. x y a.b Expression pathExpression = this.createPathExpression( configureAST( new PropertyExpression(baseExpr, this.createConstantExpression(primaryExpr)), primaryExpr ), ctx.pathElement() ); return configureAST(pathExpression, ctx); } // e.g. x y a return configureAST( new PropertyExpression( baseExpr, primaryExpr instanceof VariableExpression ? this.createConstantExpression(primaryExpr) : primaryExpr ), primaryExpr ); } // expression { ------------------------------------------------------------ @Override public ClassNode visitCastParExpression(final CastParExpressionContext ctx) { return this.visitType(ctx.type()); } @Override public Expression visitParExpression(final ParExpressionContext ctx) { Expression expression = this.visitExpressionInPar(ctx.expressionInPar()); expression.getNodeMetaData(INSIDE_PARENTHESES_LEVEL, k -> new java.util.concurrent.atomic.AtomicInteger()).getAndAdd(1); return configureAST(expression, ctx); } @Override public Expression visitExpressionInPar(final ExpressionInParContext ctx) { return this.visitEnhancedStatementExpression(ctx.enhancedStatementExpression()); } @Override public Expression visitEnhancedExpression(final EnhancedExpressionContext ctx) { Expression expression; if (asBoolean(ctx.expression())) { expression = (Expression) this.visit(ctx.expression()); } else if (asBoolean(ctx.standardLambdaExpression())) { expression = this.visitStandardLambdaExpression(ctx.standardLambdaExpression()); } else { throw createParsingFailedException("Unsupported enhanced expression: " + ctx.getText(), ctx); } return configureAST(expression, ctx); } @Override public Expression visitEnhancedStatementExpression(final EnhancedStatementExpressionContext ctx) { Expression expression; if (asBoolean(ctx.statementExpression())) { expression = ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression(); } else if (asBoolean(ctx.standardLambdaExpression())) { expression = this.visitStandardLambdaExpression(ctx.standardLambdaExpression()); } else { throw createParsingFailedException("Unsupported enhanced statement expression: " + ctx.getText(), ctx); } return configureAST(expression, ctx); } @Override public Expression visitPathExpression(final PathExpressionContext ctx) { final TerminalNode staticTerminalNode = ctx.STATIC(); Expression primaryExpr; if (asBoolean(staticTerminalNode)) { primaryExpr = configureAST(new VariableExpression(staticTerminalNode.getText()), staticTerminalNode); } else { primaryExpr = (Expression) this.visit(ctx.primary()); } return this.createPathExpression(primaryExpr, ctx.pathElement()); } @Override public Expression visitPathElement(final 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())) { boolean isSafeChain = isTrue(baseExpr, PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN); return this.createDotExpression(ctx, baseExpr, namePartExpr, genericsTypes, isSafeChain); } else if (asBoolean(ctx.SAFE_DOT())) { return this.createDotExpression(ctx, baseExpr, namePartExpr, genericsTypes, true); } else if (asBoolean(ctx.SAFE_CHAIN_DOT())) { // e.g. obj??.a OR obj??.@a Expression expression = createDotExpression(ctx, baseExpr, namePartExpr, genericsTypes, true); expression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN, Boolean.TRUE); return expression; } else if (asBoolean(ctx.METHOD_POINTER())) { // e.g. obj.&m return configureAST(new MethodPointerExpression(baseExpr, namePartExpr), ctx); } else if (asBoolean(ctx.METHOD_REFERENCE())) { // e.g. obj::m return 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 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 configureAST(propertyExpression, ctx); } } } else if (asBoolean(ctx.creator())) { CreatorContext creatorContext = ctx.creator(); creatorContext.putNodeMetaData(ENCLOSING_INSTANCE_EXPRESSION, baseExpr); return configureAST(this.visitCreator(creatorContext), ctx); } else if (asBoolean(ctx.indexPropertyArgs())) { // e.g. list[1, 3, 5] Tuple2 tuple = this.visitIndexPropertyArgs(ctx.indexPropertyArgs()); boolean isSafeChain = isTrue(baseExpr, PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN); return configureAST( new BinaryExpression(baseExpr, createGroovyToken(tuple.getV1()), tuple.getV2(), isSafeChain || asBoolean(ctx.indexPropertyArgs().SAFE_INDEX())), ctx); } else if (asBoolean(ctx.namedPropertyArgs())) { // this is a special way to signify a cast, e.g. Person[name: 'Daniel.Sun', location: 'Shanghai'] List mapEntryExpressionList = this.visitNamedPropertyArgs(ctx.namedPropertyArgs()); Expression right; Expression firstKeyExpression; int mapEntryExpressionListSize = mapEntryExpressionList.size(); if (mapEntryExpressionListSize == 0) { // expecting list of MapEntryExpressions later so use SpreadMap to smuggle empty MapExpression to later stages right = configureAST( new SpreadMapExpression(configureAST(new MapExpression(), ctx.namedPropertyArgs())), ctx.namedPropertyArgs()); } else if (mapEntryExpressionListSize == 1 && (firstKeyExpression = mapEntryExpressionList.get(0).getKeyExpression()) instanceof SpreadMapExpression) { right = firstKeyExpression; } else { ListExpression listExpression = 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; } NamedPropertyArgsContext namedPropertyArgsContext = ctx.namedPropertyArgs(); Token token = (namedPropertyArgsContext.LBRACK() == null ? namedPropertyArgsContext.SAFE_INDEX() : namedPropertyArgsContext.LBRACK()).getSymbol(); return configureAST( new BinaryExpression(baseExpr, createGroovyToken(token), right), ctx); } else if (asBoolean(ctx.arguments())) { Expression argumentsExpr = this.visitArguments(ctx.arguments()); configureAST(argumentsExpr, ctx); if (isInsideParentheses(baseExpr)) { // e.g. (obj.x)(), (obj.@x)() return configureAST(createCallMethodCallExpression(baseExpr, argumentsExpr), 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 return configureAST(createCallMethodCallExpression(attributeExpression, argumentsExpr, true), ctx); } if (baseExpr instanceof PropertyExpression) { // e.g. obj.a(1, 2) MethodCallExpression methodCallExpression = this.createMethodCallExpression((PropertyExpression) baseExpr, argumentsExpr); return 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() return configureAST(this.createCallMethodCallExpression(this.createConstantExpression(baseExpr), argumentsExpr), ctx); } else if (isPrimitiveType(baseExprText)) { // e.g. int(), long(), float(), etc. throw this.createParsingFailedException("Primitive type literal: " + baseExprText + " cannot be used as a method name", ctx); } } if (baseExpr instanceof VariableExpression // e.g. m() || baseExpr instanceof GStringExpression // e.g. "$m"() || (baseExpr instanceof ConstantExpression && isTrue(baseExpr, IS_STRING))) { // e.g. "m"() String baseExprText = baseExpr.getText(); if (THIS_STR.equals(baseExprText) || SUPER_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 (visitingClosureCount > 0) { return configureAST( new MethodCallExpression( baseExpr, baseExprText, argumentsExpr ), ctx); } return configureAST( new ConstructorCallExpression( SUPER_STR.equals(baseExprText) ? ClassNode.SUPER : ClassNode.THIS, argumentsExpr ), ctx); } MethodCallExpression methodCallExpression = this.createMethodCallExpression(baseExpr, argumentsExpr); return configureAST(methodCallExpression, ctx); } // e.g. 1(), 1.1(), ((int) 1 / 2)(1, 2), {a, b -> a + b }(1, 2), m()() return configureAST(this.createCallMethodCallExpression(baseExpr, argumentsExpr), ctx); } else if (asBoolean(ctx.closureOrLambdaExpression())) { ClosureExpression closureExpression = this.visitClosureOrLambdaExpression(ctx.closureOrLambdaExpression()); 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 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( configureAST( new ArgumentListExpression( configureAST( new MapExpression(namedArgumentListExpression.getMapEntryExpressions()), namedArgumentListExpression ), closureExpression ), tupleExpression ) ); } else { // the branch should never reach, because named arguments must not be empty methodCallExpression.setArguments( configureAST( new ArgumentListExpression(closureExpression), tupleExpression ) ); } return configureAST(methodCallExpression, ctx); } } if (baseExpr instanceof PropertyExpression) { // e.g. obj.m { } MethodCallExpression methodCallExpression = this.createMethodCallExpression( (PropertyExpression) baseExpr, configureAST( new ArgumentListExpression(closureExpression), closureExpression ) ); return configureAST(methodCallExpression, ctx); } if (baseExpr instanceof VariableExpression // e.g. m { } || baseExpr instanceof GStringExpression // e.g. "$m" { } || (baseExpr instanceof ConstantExpression && isTrue(baseExpr, IS_STRING))) { // e.g. "m" { } MethodCallExpression methodCallExpression = this.createMethodCallExpression( baseExpr, configureAST( new ArgumentListExpression(closureExpression), closureExpression ) ); return configureAST(methodCallExpression, ctx); } // e.g. 1 { }, 1.1 { }, (1 / 2) { }, m() { }, { -> ... } { } MethodCallExpression methodCallExpression = this.createCallMethodCallExpression( baseExpr, configureAST( new ArgumentListExpression(closureExpression), closureExpression) ); return configureAST(methodCallExpression, ctx); } throw createParsingFailedException("Unsupported path element: " + ctx.getText(), ctx); } private Expression createDotExpression(final PathElementContext ctx, final Expression baseExpr, final Expression namePartExpr, final GenericsType[] genericsTypes, final boolean safe) { if (asBoolean(ctx.AT())) { // e.g. obj.@a OR obj?.@a return configureAST(new AttributeExpression(baseExpr, namePartExpr, safe), ctx); } else { // e.g. obj.p OR obj?.p PropertyExpression propertyExpression = new PropertyExpression(baseExpr, namePartExpr, safe); propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, genericsTypes); return configureAST(propertyExpression, ctx); } } private MethodCallExpression createCallMethodCallExpression(final Expression baseExpr, final Expression argumentsExpr) { return createCallMethodCallExpression(baseExpr, argumentsExpr, false); } private MethodCallExpression createCallMethodCallExpression(final Expression baseExpr, final Expression argumentsExpr, final boolean implicitThis) { MethodCallExpression methodCallExpression = new MethodCallExpression(baseExpr, CALL_STR, argumentsExpr); methodCallExpression.setImplicitThis(implicitThis); return methodCallExpression; } @Override public GenericsType[] visitNonWildcardTypeArguments(final NonWildcardTypeArgumentsContext ctx) { if (!asBoolean(ctx)) { return null; } return Arrays.stream(this.visitTypeList(ctx.typeList())) .map(this::createGenericsType) .toArray(GenericsType[]::new); } @Override public ClassNode[] visitTypeList(final TypeListContext ctx) { if (!asBoolean(ctx)) { return ClassNode.EMPTY_ARRAY; } return ctx.type().stream() .map(this::visitType) .toArray(ClassNode[]::new); } @Override public Expression visitArguments(final ArgumentsContext ctx) { if (asBoolean(ctx) && asBoolean(ctx.COMMA()) && !asBoolean(ctx.enhancedArgumentListInPar())) { throw createParsingFailedException("Expression expected", ctx.COMMA()); } if (!asBoolean(ctx) || !asBoolean(ctx.enhancedArgumentListInPar())) { return new ArgumentListExpression(); } return configureAST(this.visitEnhancedArgumentListInPar(ctx.enhancedArgumentListInPar()), ctx); } @Override public Expression visitEnhancedArgumentListInPar(final EnhancedArgumentListInParContext ctx) { if (!asBoolean(ctx)) { return null; } List expressionList = new LinkedList<>(); List mapEntryExpressionList = new LinkedList<>(); ctx.enhancedArgumentListElement().stream() .map(this::visitEnhancedArgumentListElement) .forEach(e -> { if (e instanceof MapEntryExpression) { MapEntryExpression mapEntryExpression = (MapEntryExpression) e; validateDuplicatedNamedParameter(mapEntryExpressionList, mapEntryExpression); mapEntryExpressionList.add(mapEntryExpression); } else { expressionList.add(e); } }); if (!asBoolean(mapEntryExpressionList)) { // e.g. arguments like 1, 2 OR someArg, e -> e return configureAST( new ArgumentListExpression(expressionList), ctx); } if (!asBoolean(expressionList)) { // e.g. arguments like x: 1, y: 2 return configureAST( new TupleExpression( 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, configureAST(new MapExpression(mapEntryExpressionList), ctx)); return configureAST(argumentListExpression, ctx); } throw createParsingFailedException("Unsupported argument list: " + ctx.getText(), ctx); } private void validateDuplicatedNamedParameter(final List mapEntryExpressionList, final MapEntryExpression mapEntryExpression) { Expression keyExpression = mapEntryExpression.getKeyExpression(); if (keyExpression == null || isInsideParentheses(keyExpression)) { return; } String parameterName = keyExpression.getText(); boolean isDuplicatedNamedParameter = mapEntryExpressionList.stream() .anyMatch(m -> m.getKeyExpression().getText().equals(parameterName)); if (!isDuplicatedNamedParameter) { return; } throw createParsingFailedException("Duplicated named parameter '" + parameterName + "' found", mapEntryExpression); } @Override public Expression visitEnhancedArgumentListElement(final EnhancedArgumentListElementContext ctx) { if (asBoolean(ctx.expressionListElement())) { return configureAST(this.visitExpressionListElement(ctx.expressionListElement()), ctx); } if (asBoolean(ctx.standardLambdaExpression())) { return configureAST(this.visitStandardLambdaExpression(ctx.standardLambdaExpression()), ctx); } if (asBoolean(ctx.mapEntry())) { return configureAST(this.visitMapEntry(ctx.mapEntry()), ctx); } throw createParsingFailedException("Unsupported enhanced argument list element: " + ctx.getText(), ctx); } @Override public ConstantExpression visitStringLiteral(final StringLiteralContext ctx) { String text = parseStringLiteral(ctx.StringLiteral().getText()); ConstantExpression constantExpression = new ConstantExpression(text); constantExpression.putNodeMetaData(IS_STRING, Boolean.TRUE); return configureAST(constantExpression, ctx); } private String parseStringLiteral(String text) { int slashyType = getSlashyType(text); boolean startsWithSlash = false; if (text.startsWith(TSQ_STR) || text.startsWith(TDQ_STR)) { text = StringUtils.removeCR(text); // remove CR in the multiline string text = StringUtils.trimQuotations(text, 3); } else if (text.startsWith(SQ_STR) || text.startsWith(DQ_STR) || (startsWithSlash = text.startsWith(SLASH_STR))) { if (startsWithSlash) { // 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 = StringUtils.trimQuotations(text, 1); } else if (text.startsWith(DOLLAR_SLASH_STR)) { text = StringUtils.removeCR(text); text = StringUtils.trimQuotations(text, 2); } //handle escapes. return StringUtils.replaceEscapes(text, slashyType); } private int getSlashyType(final String text) { return text.startsWith(SLASH_STR) ? StringUtils.SLASHY : text.startsWith(DOLLAR_SLASH_STR) ? StringUtils.DOLLAR_SLASHY : StringUtils.NONE_SLASHY; } @Override public Tuple2 visitIndexPropertyArgs(final IndexPropertyArgsContext ctx) { List expressionList = this.visitExpressionList(ctx.expressionList()); Token token = (ctx.LBRACK() == null ? ctx.SAFE_INDEX() : ctx.LBRACK()).getSymbol(); 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 tuple(token, indexExpr); } // e.g. a[1, 2] ListExpression listExpression = new ListExpression(expressionList); listExpression.setWrapped(true); return tuple(token, configureAST(listExpression, ctx)); } @Override public List visitNamedPropertyArgs(final NamedPropertyArgsContext ctx) { return this.visitMapEntryList(ctx.mapEntryList()); } @Override public Expression visitNamePart(final NamePartContext ctx) { if (asBoolean(ctx.identifier())) { return configureAST(new ConstantExpression(this.visitIdentifier(ctx.identifier())), ctx); } else if (asBoolean(ctx.stringLiteral())) { return configureAST(this.visitStringLiteral(ctx.stringLiteral()), ctx); } else if (asBoolean(ctx.dynamicMemberName())) { return configureAST(this.visitDynamicMemberName(ctx.dynamicMemberName()), ctx); } else if (asBoolean(ctx.keywords())) { return configureAST(new ConstantExpression(ctx.keywords().getText()), ctx); } throw createParsingFailedException("Unsupported name part: " + ctx.getText(), ctx); } @Override public Expression visitDynamicMemberName(final DynamicMemberNameContext ctx) { if (asBoolean(ctx.parExpression())) { return configureAST(this.visitParExpression(ctx.parExpression()), ctx); } else if (asBoolean(ctx.gstring())) { return configureAST(this.visitGstring(ctx.gstring()), ctx); } throw createParsingFailedException("Unsupported dynamic member name: " + ctx.getText(), ctx); } @Override public Expression visitPostfixExpression(final PostfixExpressionContext ctx) { Expression pathExpr = this.visitPathExpression(ctx.pathExpression()); if (asBoolean(ctx.op)) { PostfixExpression postfixExpression = new PostfixExpression(pathExpr, createGroovyToken(ctx.op)); if (visitingAssertStatementCount > 0) { // powerassert requires different column for values, so we have to copy the location of op return configureAST(postfixExpression, ctx.op); } else { return configureAST(postfixExpression, ctx); } } return configureAST(pathExpr, ctx); } @Override public Expression visitUnaryNotExprAlt(final UnaryNotExprAltContext ctx) { if (asBoolean(ctx.NOT())) { return configureAST( new NotExpression((Expression) this.visit(ctx.expression())), ctx); } if (asBoolean(ctx.BITNOT())) { return configureAST( new BitwiseNegationExpression((Expression) this.visit(ctx.expression())), ctx); } throw createParsingFailedException("Unsupported unary expression: " + ctx.getText(), ctx); } @Override public CastExpression visitCastExprAlt(final CastExprAltContext ctx) { Expression expr = (Expression) this.visit(ctx.expression()); if (expr instanceof VariableExpression && ((VariableExpression) expr).isSuperExpression()) { this.createParsingFailedException("Cannot cast or coerce `super`", ctx); // GROOVY-9391 } CastExpression cast = new CastExpression(this.visitCastParExpression(ctx.castParExpression()), expr); return configureAST(cast, ctx); } @Override public BinaryExpression visitPowerExprAlt(final PowerExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public Expression visitUnaryAddExprAlt(final UnaryAddExprAltContext ctx) { Expression expression = (Expression) this.visit(ctx.expression()); switch (ctx.op.getType()) { case ADD: if (this.isNonStringConstantOutsideParentheses(expression)) { return configureAST(expression, ctx); } return configureAST(new UnaryPlusExpression(expression), ctx); case SUB: if (this.isNonStringConstantOutsideParentheses(expression)) { ConstantExpression constantExpression = (ConstantExpression) expression; try { String integerLiteralText = constantExpression.getNodeMetaData(INTEGER_LITERAL_TEXT); if (integerLiteralText != null) { ConstantExpression result = new ConstantExpression(Numbers.parseInteger(SUB_STR + integerLiteralText), true); this.numberFormatError = null; // reset return configureAST(result, ctx); } String floatingPointLiteralText = constantExpression.getNodeMetaData(FLOATING_POINT_LITERAL_TEXT); if (floatingPointLiteralText != null) { ConstantExpression result = new ConstantExpression(Numbers.parseDecimal(SUB_STR + floatingPointLiteralText), true); this.numberFormatError = null; // reset return configureAST(result, ctx); } } catch (Exception e) { throw this.createParsingFailedException(e.getMessage(), ctx); } throw new GroovyBugError("Failed to find the original number literal text: " + constantExpression.getText()); } return configureAST(new UnaryMinusExpression(expression), ctx); case INC: case DEC: return configureAST(new PrefixExpression(this.createGroovyToken(ctx.op), expression), ctx); default: throw this.createParsingFailedException("Unsupported unary operation: " + ctx.getText(), ctx); } } private boolean isNonStringConstantOutsideParentheses(final Expression expression) { return expression instanceof ConstantExpression && !(((ConstantExpression) expression).getValue() instanceof String) && !isInsideParentheses(expression); } @Override public BinaryExpression visitMultiplicativeExprAlt(final MultiplicativeExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public BinaryExpression visitAdditiveExprAlt(final AdditiveExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public Expression visitShiftExprAlt(final ShiftExprAltContext ctx) { Expression left = (Expression) this.visit(ctx.left); Expression right = (Expression) this.visit(ctx.right); if (asBoolean(ctx.rangeOp)) { return configureAST(new RangeExpression(left, right, ctx.rangeOp.getText().startsWith("<"), ctx.rangeOp.getText().endsWith("<")), ctx); } org.codehaus.groovy.syntax.Token op; Token antlrToken; if (asBoolean(ctx.dlOp)) { op = this.createGroovyToken(ctx.dlOp, 2); antlrToken = ctx.dlOp; } else if (asBoolean(ctx.dgOp)) { op = this.createGroovyToken(ctx.dgOp, 2); antlrToken = ctx.dgOp; } else if (asBoolean(ctx.tgOp)) { op = this.createGroovyToken(ctx.tgOp, 3); antlrToken = ctx.tgOp; } else { throw createParsingFailedException("Unsupported shift expression: " + ctx.getText(), ctx); } BinaryExpression binaryExpression = new BinaryExpression(left, op, right); if (isTrue(ctx, IS_INSIDE_CONDITIONAL_EXPRESSION)) { return configureAST(binaryExpression, antlrToken); } return configureAST(binaryExpression, ctx); } @Override public Expression visitRelationalExprAlt(final RelationalExprAltContext ctx) { switch (ctx.op.getType()) { case AS: Expression expr = (Expression) this.visit(ctx.left); if (expr instanceof VariableExpression && ((VariableExpression) expr).isSuperExpression()) { this.createParsingFailedException("Cannot cast or coerce `super`", ctx); // GROOVY-9391 } CastExpression cast = CastExpression.asExpression(this.visitType(ctx.type()), expr); return configureAST(cast, ctx); case INSTANCEOF: case NOT_INSTANCEOF: ctx.type().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE); return configureAST( new BinaryExpression( (Expression) this.visit(ctx.left), this.createGroovyToken(ctx.op), configureAST(new ClassExpression(this.visitType(ctx.type())), ctx.type())), ctx); case GT: case GE: case LT: case LE: case IN: case NOT_IN: return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); default: throw this.createParsingFailedException("Unsupported relational expression: " + ctx.getText(), ctx); } } @Override public BinaryExpression visitEqualityExprAlt(final EqualityExprAltContext ctx) { return configureAST( this.createBinaryExpression(ctx.left, ctx.op, ctx.right), ctx); } @Override public BinaryExpression visitRegexExprAlt(final RegexExprAltContext ctx) { return configureAST( this.createBinaryExpression(ctx.left, ctx.op, ctx.right), ctx); } @Override public BinaryExpression visitAndExprAlt(final AndExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public BinaryExpression visitExclusiveOrExprAlt(final ExclusiveOrExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public BinaryExpression visitInclusiveOrExprAlt(final InclusiveOrExprAltContext ctx) { return this.createBinaryExpression(ctx.left, ctx.op, ctx.right, ctx); } @Override public BinaryExpression visitLogicalAndExprAlt(final LogicalAndExprAltContext ctx) { return configureAST( this.createBinaryExpression(ctx.left, ctx.op, ctx.right), ctx); } @Override public BinaryExpression visitLogicalOrExprAlt(final LogicalOrExprAltContext ctx) { return configureAST( this.createBinaryExpression(ctx.left, ctx.op, ctx.right), ctx); } @Override public BinaryExpression visitImplicationExprAlt(final ImplicationExprAltContext ctx) { return configureAST( this.createBinaryExpression(ctx.left, ctx.op, ctx.right), ctx); } @Override public Expression visitConditionalExprAlt(final ConditionalExprAltContext ctx) { ctx.fb.putNodeMetaData(IS_INSIDE_CONDITIONAL_EXPRESSION, Boolean.TRUE); if (asBoolean(ctx.ELVIS())) { // e.g. a == 6 ?: 0 return configureAST( new ElvisOperatorExpression((Expression) this.visit(ctx.con), (Expression) this.visit(ctx.fb)), ctx); } ctx.tb.putNodeMetaData(IS_INSIDE_CONDITIONAL_EXPRESSION, Boolean.TRUE); return configureAST( new TernaryExpression( configureAST(new BooleanExpression((Expression) this.visit(ctx.con)), ctx.con), (Expression) this.visit(ctx.tb), (Expression) this.visit(ctx.fb)), ctx); } @Override public BinaryExpression visitMultipleAssignmentExprAlt(final MultipleAssignmentExprAltContext ctx) { return configureAST( new BinaryExpression( this.visitVariableNames(ctx.left), this.createGroovyToken(ctx.op), ((ExpressionStatement) this.visit(ctx.right)).getExpression()), ctx); } @Override public BinaryExpression visitAssignmentExprAlt(final AssignmentExprAltContext ctx) { Expression leftExpr = (Expression) this.visit(ctx.left); if (leftExpr instanceof VariableExpression && isInsideParentheses(leftExpr)) { // it is a special multiple assignment whose variable count is only one, e.g. (a) = [1] if (leftExpr.getNodeMetaData(INSIDE_PARENTHESES_LEVEL).intValue() > 1) { throw createParsingFailedException("Nested parenthesis is not allowed in multiple assignment, e.g. ((a)) = b", ctx); } return configureAST( new BinaryExpression( configureAST(new TupleExpression(leftExpr), ctx.left), this.createGroovyToken(ctx.op), (Expression) this.visit(ctx.right)), ctx); } // the LHS expression should be a variable which is not inside any parentheses if ( !( (leftExpr instanceof VariableExpression // && !(THIS_STR.equals(leftExpr.getText()) || SUPER_STR.equals(leftExpr.getText())) // commented, e.g. this = value // this will be transformed to $this && !isInsideParentheses(leftExpr)) // e.g. p = 123 || leftExpr instanceof PropertyExpression // e.g. obj.p = 123 || (leftExpr instanceof BinaryExpression // && !(((BinaryExpression) leftExpr).getRightExpression() instanceof ListExpression) // commented, e.g. list[1, 2] = [11, 12] && Types.LEFT_SQUARE_BRACKET == ((BinaryExpression) leftExpr).getOperation().getType()) // e.g. map[a] = 123 OR map['a'] = 123 OR map["$a"] = 123 ) ) { throw createParsingFailedException("The LHS of an assignment should be a variable or a field accessing expression", ctx); } return configureAST( new BinaryExpression( leftExpr, this.createGroovyToken(ctx.op), (Expression) this.visit(ctx.right)), ctx); } // } expression ------------------------------------------------------------ // primary { --------------------------------------------------------------- @Override public Expression visitIdentifierPrmrAlt(final IdentifierPrmrAltContext ctx) { if (asBoolean(ctx.typeArguments())) { ClassNode classNode = ClassHelper.make(ctx.identifier().getText()); classNode.setGenericsTypes( this.visitTypeArguments(ctx.typeArguments())); return configureAST(new ClassExpression(classNode), ctx); } return configureAST(new VariableExpression(this.visitIdentifier(ctx.identifier())), ctx); } @Override public Expression visitNewPrmrAlt(final NewPrmrAltContext ctx) { return configureAST(this.visitCreator(ctx.creator()), ctx); } @Override public VariableExpression visitThisPrmrAlt(final ThisPrmrAltContext ctx) { return configureAST(new VariableExpression(ctx.THIS().getText()), ctx); } @Override public VariableExpression visitSuperPrmrAlt(final SuperPrmrAltContext ctx) { return configureAST(new VariableExpression(ctx.SUPER().getText()), ctx); } // } primary --------------------------------------------------------------- @Override public Expression visitCreator(final CreatorContext ctx) { ClassNode classNode = this.visitCreatedName(ctx.createdName()); if (asBoolean(ctx.arguments())) { // create instance of class Expression arguments = this.visitArguments(ctx.arguments()); Expression enclosingInstanceExpression = ctx.getNodeMetaData(ENCLOSING_INSTANCE_EXPRESSION); if (enclosingInstanceExpression != null) { if (arguments instanceof ArgumentListExpression) { ((ArgumentListExpression) arguments).getExpressions().add(0, enclosingInstanceExpression); } else if (arguments instanceof TupleExpression) { throw createParsingFailedException("Creating instance of non-static class does not support named parameters", arguments); } else if (arguments instanceof NamedArgumentListExpression) { throw createParsingFailedException("Unexpected arguments", arguments); } else { throw createParsingFailedException("Unsupported arguments", arguments); // should never reach here } if (enclosingInstanceExpression instanceof ConstructorCallExpression && classNode.getName().indexOf('.') < 0) { classNode.setName(enclosingInstanceExpression.getType().getName() + '.' + classNode.getName()); // GROOVY-8947 } } if (asBoolean(ctx.anonymousInnerClassDeclaration())) { ctx.anonymousInnerClassDeclaration().putNodeMetaData(ANONYMOUS_INNER_CLASS_SUPER_CLASS, classNode); InnerClassNode anonymousInnerClassNode = this.visitAnonymousInnerClassDeclaration(ctx.anonymousInnerClassDeclaration()); List anonymousInnerClassList = anonymousInnerClassesDefinedInMethodStack.peek(); if (anonymousInnerClassList != null) { // if the anonymous class is created in a script, no anonymousInnerClassList is available. anonymousInnerClassList.add(anonymousInnerClassNode); } ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(anonymousInnerClassNode, arguments); constructorCallExpression.setUsingAnonymousInnerClass(true); return configureAST(constructorCallExpression, ctx); } ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(classNode, arguments); return configureAST(constructorCallExpression, ctx); } if (asBoolean(ctx.dim())) { // create array ArrayExpression arrayExpression; List, TerminalNode>> dimList = ctx.dim().stream() .map(this::visitDim) .collect(Collectors.toList()); TerminalNode invalidDimLBrack = null; Boolean exprEmpty = null; List, TerminalNode>> emptyDimList = new LinkedList<>(); List, TerminalNode>> dimWithExprList = new LinkedList<>(); Tuple3, TerminalNode> latestDim = null; for (Tuple3, TerminalNode> dim : dimList) { if (null == dim.getV1()) { emptyDimList.add(dim); exprEmpty = Boolean.TRUE; } else { if (Boolean.TRUE.equals(exprEmpty)) { invalidDimLBrack = latestDim.getV3(); } dimWithExprList.add(dim); exprEmpty = Boolean.FALSE; } latestDim = dim; } if (asBoolean(ctx.arrayInitializer())) { if (!dimWithExprList.isEmpty()) { throw createParsingFailedException("dimension should be empty", dimWithExprList.get(0).getV3()); } ClassNode elementType = classNode; for (int i = 0, n = emptyDimList.size() - 1; i < n; i += 1) { elementType = this.createArrayType(elementType); } arrayExpression = new ArrayExpression( elementType, this.visitArrayInitializer(ctx.arrayInitializer())); } else { if (null != invalidDimLBrack) { throw createParsingFailedException("dimension cannot be empty", invalidDimLBrack); } if (dimWithExprList.isEmpty() && !emptyDimList.isEmpty()) { throw createParsingFailedException("dimensions cannot be all empty", emptyDimList.get(0).getV3()); } Expression[] empties; if (asBoolean(emptyDimList)) { empties = new Expression[emptyDimList.size()]; Arrays.fill(empties, ConstantExpression.EMPTY_EXPRESSION); } else { empties = Expression.EMPTY_ARRAY; } arrayExpression = new ArrayExpression( classNode, null, Stream.concat( dimWithExprList.stream().map(Tuple3::getV1), Arrays.stream(empties) ).collect(Collectors.toList())); } arrayExpression.setType( this.createArrayType( classNode, dimList.stream().map(Tuple3::getV2).collect(Collectors.toList()) ) ); return configureAST(arrayExpression, ctx); } throw createParsingFailedException("Unsupported creator: " + ctx.getText(), ctx); } @Override public Tuple3, TerminalNode> visitDim(final DimContext ctx) { return tuple((Expression) this.visit(ctx.expression()), this.visitAnnotationsOpt(ctx.annotationsOpt()), ctx.LBRACK()); } private static String nextAnonymousClassName(final ClassNode outerClass) { int anonymousClassCount = 0; for (Iterator it = outerClass.getInnerClasses(); it.hasNext();) { InnerClassNode innerClass = it.next(); if (innerClass.isAnonymous()) { anonymousClassCount += 1; } } return outerClass.getName() + "$" + (anonymousClassCount + 1); } @Override public InnerClassNode visitAnonymousInnerClassDeclaration(final AnonymousInnerClassDeclarationContext ctx) { ClassNode superClass = Objects.requireNonNull(ctx.getNodeMetaData(ANONYMOUS_INNER_CLASS_SUPER_CLASS), "superClass should not be null"); ClassNode outerClass = Optional.ofNullable(this.classNodeStack.peek()).orElse(this.moduleNode.getScriptClassDummy()); String innerClassName = nextAnonymousClassName(outerClass); InnerClassNode anonymousInnerClass; if (ctx.t == 1) { anonymousInnerClass = new EnumConstantClassNode(outerClass, innerClassName, superClass.getPlainNodeReference()); // and remove the final modifier from superClass to allow the sub class superClass.setModifiers(superClass.getModifiers() & ~Opcodes.ACC_FINAL); } else { anonymousInnerClass = new InnerClassNode(outerClass, innerClassName, Opcodes.ACC_PUBLIC, superClass); } anonymousInnerClass.setAnonymous(true); anonymousInnerClass.setUsingGenerics(false); anonymousInnerClass.putNodeMetaData(CLASS_NAME, innerClassName); configureAST(anonymousInnerClass, ctx); this.classNodeStack.push(anonymousInnerClass); ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, anonymousInnerClass); this.visitClassBody(ctx.classBody()); this.classNodeStack.pop(); if (this.classNodeStack.isEmpty()) this.addToClassNodeList(anonymousInnerClass); return anonymousInnerClass; } @Override public ClassNode visitCreatedName(final CreatedNameContext ctx) { ClassNode classNode = null; if (asBoolean(ctx.qualifiedClassName())) { classNode = this.visitQualifiedClassName(ctx.qualifiedClassName()); if (asBoolean(ctx.typeArgumentsOrDiamond())) { classNode.setGenericsTypes( this.visitTypeArgumentsOrDiamond(ctx.typeArgumentsOrDiamond())); configureAST(classNode, ctx); } } else if (asBoolean(ctx.primitiveType())) { classNode = configureAST(this.visitPrimitiveType(ctx.primitiveType()), ctx); } if (classNode == null) { throw createParsingFailedException("Unsupported created name: " + ctx.getText(), ctx); } classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); // GROOVY-11178 return classNode; } @Override public MapExpression visitMap(final MapContext ctx) { return configureAST( new MapExpression(this.visitMapEntryList(ctx.mapEntryList())), ctx); } @Override public List visitMapEntryList(final MapEntryListContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } return this.createMapEntryList(ctx.mapEntry()); } private List createMapEntryList(final List mapEntryContextList) { if (!asBoolean(mapEntryContextList)) { return Collections.emptyList(); } return mapEntryContextList.stream() .map(this::visitMapEntry) .collect(Collectors.toList()); } @Override public MapEntryExpression visitMapEntry(final MapEntryContext ctx) { Expression keyExpr; Expression valueExpr = this.visitEnhancedExpression(ctx.enhancedExpression()); if (asBoolean(ctx.MUL())) { keyExpr = configureAST(new SpreadMapExpression(valueExpr), ctx); } else if (asBoolean(ctx.mapEntryLabel())) { keyExpr = this.visitMapEntryLabel(ctx.mapEntryLabel()); } else { throw createParsingFailedException("Unsupported map entry: " + ctx.getText(), ctx); } return configureAST( new MapEntryExpression(keyExpr, valueExpr), ctx); } @Override public Expression visitMapEntryLabel(final MapEntryLabelContext ctx) { if (asBoolean(ctx.keywords())) { return configureAST(this.visitKeywords(ctx.keywords()), ctx); } else if (asBoolean(ctx.primary())) { Expression expression = (Expression) this.visit(ctx.primary()); // if the key is variable and not inside parentheses, convert it to a constant, e.g. [a:1, b:2] if (expression instanceof VariableExpression && !isInsideParentheses(expression)) { expression = configureAST( new ConstantExpression(((VariableExpression) expression).getName()), expression); } return configureAST(expression, ctx); } throw createParsingFailedException("Unsupported map entry label: " + ctx.getText(), ctx); } @Override public ConstantExpression visitKeywords(final KeywordsContext ctx) { return configureAST(new ConstantExpression(ctx.getText()), ctx); } @Override public VariableExpression visitBuiltInType(final BuiltInTypeContext ctx) { String text; if (asBoolean(ctx.VOID())) { text = ctx.VOID().getText(); } else if (asBoolean(ctx.BuiltInPrimitiveType())) { text = ctx.BuiltInPrimitiveType().getText(); } else { throw createParsingFailedException("Unsupported built-in type: " + ctx, ctx); } final VariableExpression variableExpression = new VariableExpression(text); variableExpression.setNodeMetaData(IS_BUILT_IN_TYPE, Boolean.TRUE); return configureAST(variableExpression, ctx); } @Override public ListExpression visitList(final ListContext ctx) { if (asBoolean(ctx.COMMA()) && !asBoolean(ctx.expressionList())) { throw createParsingFailedException("Empty list constructor should not contain any comma(,)", ctx.COMMA()); } return configureAST( new ListExpression( this.visitExpressionList(ctx.expressionList())), ctx); } @Override public List visitExpressionList(final ExpressionListContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } return this.createExpressionList(ctx.expressionListElement()); } private List createExpressionList(final List expressionListElementContextList) { if (!asBoolean(expressionListElementContextList)) { return Collections.emptyList(); } return expressionListElementContextList.stream() .map(this::visitExpressionListElement) .collect(Collectors.toList()); } @Override public Expression visitExpressionListElement(final ExpressionListElementContext ctx) { Expression expression = (Expression) this.visit(ctx.expression()); validateExpressionListElement(ctx, expression); if (asBoolean(ctx.MUL())) { if (!ctx.canSpread) { throw createParsingFailedException("spread operator is not allowed here", ctx.MUL()); } return configureAST(new SpreadExpression(expression), ctx); } return configureAST(expression, ctx); } private void validateExpressionListElement(final ExpressionListElementContext ctx, final Expression expression) { if (expression instanceof MethodCallExpression && isTrue(expression, IS_COMMAND_EXPRESSION)) { // statements like `foo(String a)` is invalid MethodCallExpression methodCallExpression = (MethodCallExpression) expression; String methodName = methodCallExpression.getMethodAsString(); if (methodCallExpression.isImplicitThis() && Character.isUpperCase(methodName.codePointAt(0)) || isPrimitiveType(methodName)) { throw createParsingFailedException("Invalid method declaration", ctx); } } } // literal { --------------------------------------------------------------- @Override public ConstantExpression visitIntegerLiteralAlt(final IntegerLiteralAltContext ctx) { String text = ctx.IntegerLiteral().getText(); Number num = null; try { num = Numbers.parseInteger(text); } catch (Exception e) { this.numberFormatError = tuple(ctx, e); } ConstantExpression constantExpression = new ConstantExpression(num, true); constantExpression.putNodeMetaData(INTEGER_LITERAL_TEXT, text); constantExpression.putNodeMetaData(IS_NUMERIC, Boolean.TRUE); return configureAST(constantExpression, ctx); } @Override public ConstantExpression visitFloatingPointLiteralAlt(final FloatingPointLiteralAltContext ctx) { String text = ctx.FloatingPointLiteral().getText(); Number num = null; try { num = Numbers.parseDecimal(text); } catch (Exception e) { this.numberFormatError = tuple(ctx, e); } ConstantExpression constantExpression = new ConstantExpression(num, true); constantExpression.putNodeMetaData(FLOATING_POINT_LITERAL_TEXT, text); constantExpression.putNodeMetaData(IS_NUMERIC, Boolean.TRUE); return configureAST(constantExpression, ctx); } @Override public ConstantExpression visitBooleanLiteralAlt(final BooleanLiteralAltContext ctx) { return configureAST(new ConstantExpression("true".equals(ctx.BooleanLiteral().getText()), true), ctx); } @Override public ConstantExpression visitNullLiteralAlt(final NullLiteralAltContext ctx) { return configureAST(new ConstantExpression(null), ctx); } // } literal --------------------------------------------------------------- // gstring { --------------------------------------------------------------- @Override public GStringExpression visitGstring(final GstringContext ctx) { final List stringLiteralList = new LinkedList<>(); final String begin = ctx.GStringBegin().getText(); final String beginQuotation = beginQuotation(begin); stringLiteralList.add(configureAST(new ConstantExpression(parseGStringBegin(ctx, beginQuotation)), ctx.GStringBegin())); List partStrings = ctx.GStringPart().stream() .map(e -> configureAST(new ConstantExpression(parseGStringPart(e, beginQuotation)), e)) .collect(Collectors.toList()); stringLiteralList.addAll(partStrings); stringLiteralList.add(configureAST(new ConstantExpression(parseGStringEnd(ctx, beginQuotation)), ctx.GStringEnd())); List values = ctx.gstringValue().stream() .map(this::visitGstringValue) .collect(Collectors.toList()); StringBuilder verbatimText = new StringBuilder(ctx.getText().length()); for (int i = 0, n = stringLiteralList.size(), s = values.size(); i < n; i += 1) { verbatimText.append(stringLiteralList.get(i).getValue()); if (i == s) { continue; } Expression value = values.get(i); if (!asBoolean(value)) { continue; } boolean isVariableExpression = value instanceof VariableExpression; verbatimText.append(DOLLAR_STR); if (!isVariableExpression) verbatimText.append('{'); verbatimText.append(value.getText()); if (!isVariableExpression) verbatimText.append('}'); } return configureAST(new GStringExpression(verbatimText.toString(), stringLiteralList, values), ctx); } private static boolean hasArrow(final GstringValueContext e) { return asBoolean(e.closure().ARROW()); } private String parseGStringEnd(final GstringContext ctx, final String beginQuotation) { StringBuilder text = new StringBuilder(ctx.GStringEnd().getText()); text.insert(0, beginQuotation); return this.parseStringLiteral(text.toString()); } private String parseGStringPart(final TerminalNode e, final String beginQuotation) { StringBuilder text = new StringBuilder(e.getText()); text.deleteCharAt(text.length() - 1); // remove the tailing $ text.insert(0, beginQuotation).append(QUOTATION_MAP.get(beginQuotation)); return this.parseStringLiteral(text.toString()); } private String parseGStringBegin(final GstringContext ctx, final String beginQuotation) { StringBuilder text = new StringBuilder(ctx.GStringBegin().getText()); text.deleteCharAt(text.length() - 1); // remove the tailing $ text.append(QUOTATION_MAP.get(beginQuotation)); return this.parseStringLiteral(text.toString()); } private static String beginQuotation(final String text) { if (text.startsWith(TDQ_STR)) { return TDQ_STR; } else if (text.startsWith(DQ_STR)) { return DQ_STR; } else if (text.startsWith(SLASH_STR)) { return SLASH_STR; } else if (text.startsWith(DOLLAR_SLASH_STR)) { return DOLLAR_SLASH_STR; } else { return String.valueOf(text.charAt(0)); } } @Override public Expression visitGstringValue(final GstringValueContext ctx) { if (asBoolean(ctx.gstringPath())) { return configureAST(this.visitGstringPath(ctx.gstringPath()), ctx); } if (asBoolean(ctx.closure())) { ClosureExpression closureExpression = this.visitClosure(ctx.closure()); if (!hasArrow(ctx)) { List statementList = ((BlockStatement) closureExpression.getCode()).getStatements(); int size = statementList.size(); if (1 == size) { Statement statement = statementList.get(0); if (statement instanceof ExpressionStatement) { Expression expression = ((ExpressionStatement) statement).getExpression(); if (!(expression instanceof DeclarationExpression)) { return expression; } } } else if (0 == size) { // e.g. "${}" return configureAST(new ConstantExpression(null), ctx); } return configureAST(this.createCallMethodCallExpression(closureExpression, new ArgumentListExpression(), true), ctx); } return configureAST(closureExpression, ctx); } throw createParsingFailedException("Unsupported gstring value: " + ctx.getText(), ctx); } @Override public Expression visitGstringPath(final GstringPathContext ctx) { VariableExpression variableExpression = new VariableExpression(this.visitIdentifier(ctx.identifier())); if (asBoolean(ctx.GStringPathPart())) { Expression propertyExpression = ctx.GStringPathPart().stream() .map(e -> configureAST((Expression) new ConstantExpression(e.getText().substring(1)), e)) .reduce(configureAST(variableExpression, ctx.identifier()), (r, e) -> configureAST(new PropertyExpression(r, e), e)); return configureAST(propertyExpression, ctx); } return configureAST(variableExpression, ctx); } // } gstring --------------------------------------------------------------- @Override public LambdaExpression visitStandardLambdaExpression(final StandardLambdaExpressionContext ctx) { switchExpressionRuleContextStack.push(ctx); try { return configureAST(this.createLambda(ctx.standardLambdaParameters(), ctx.lambdaBody()), ctx); } finally { switchExpressionRuleContextStack.pop(); } } private LambdaExpression createLambda(final StandardLambdaParametersContext standardLambdaParametersContext, final LambdaBodyContext lambdaBodyContext) { return new LambdaExpression( this.visitStandardLambdaParameters(standardLambdaParametersContext), this.visitLambdaBody(lambdaBodyContext)); } @Override public Parameter[] visitStandardLambdaParameters(final StandardLambdaParametersContext ctx) { if (asBoolean(ctx.variableDeclaratorId())) { VariableExpression variable = this.visitVariableDeclaratorId(ctx.variableDeclaratorId()); Parameter parameter = new Parameter(ClassHelper.dynamicType(), variable.getName()); configureAST(parameter, variable); return new Parameter[]{parameter}; } Parameter[] parameters = this.visitFormalParameters(ctx.formalParameters()); return (parameters.length > 0 ? parameters : null); } @Override public Statement visitLambdaBody(final LambdaBodyContext ctx) { if (asBoolean(ctx.block())) { return configureAST(this.visitBlock(ctx.block()), ctx); } return configureAST((Statement) this.visit(ctx.statementExpression()), ctx); } @Override public ClosureExpression visitClosure(final ClosureContext ctx) { switchExpressionRuleContextStack.push(ctx); visitingClosureCount += 1; try { Parameter[] parameters = asBoolean(ctx.formalParameterList()) ? this.visitFormalParameterList(ctx.formalParameterList()) : null; BlockStatement code = this.visitBlockStatementsOpt(ctx.blockStatementsOpt()); if (!asBoolean(ctx.ARROW())) { parameters = Parameter.EMPTY_ARRAY; if (code.isEmpty()) { configureAST(code, ctx); } } return configureAST(new ClosureExpression(parameters, code), ctx); } finally { switchExpressionRuleContextStack.pop(); visitingClosureCount -= 1; } } @Override public Parameter[] visitFormalParameters(final FormalParametersContext ctx) { if (!asBoolean(ctx)) { return Parameter.EMPTY_ARRAY; } return this.visitFormalParameterList(ctx.formalParameterList()); } @Override public Parameter[] visitFormalParameterList(final FormalParameterListContext ctx) { if (!asBoolean(ctx)) { return Parameter.EMPTY_ARRAY; } List parameterList = new LinkedList<>(); if (asBoolean(ctx.thisFormalParameter())) { parameterList.add(this.visitThisFormalParameter(ctx.thisFormalParameter())); } List formalParameterList = ctx.formalParameter(); if (asBoolean(formalParameterList)) { validateVarArgParameter(formalParameterList); parameterList.addAll( formalParameterList.stream() .map(this::visitFormalParameter) .collect(Collectors.toList())); } validateParameterList(parameterList); return parameterList.toArray(Parameter.EMPTY_ARRAY); } private void validateVarArgParameter(final List formalParameterList) { for (int i = 0, n = formalParameterList.size(); i < n - 1; i += 1) { FormalParameterContext formalParameterContext = formalParameterList.get(i); if (asBoolean(formalParameterContext.ELLIPSIS())) { throw createParsingFailedException("The var-arg parameter strs must be the last parameter", formalParameterContext); } } } private void validateParameterList(final List parameterList) { for (int n = parameterList.size(), i = n - 1; i >= 0; i -= 1) { Parameter parameter = parameterList.get(i); String name = parameter.getName(); if ("_".equals(name)) { continue; // check this later } for (Parameter otherParameter : parameterList) { if (otherParameter == parameter) { continue; } if (otherParameter.getName().equals(name)) { throw createParsingFailedException("Duplicated parameter '" + name + "' found.", parameter); } } } } @Override public Parameter visitFormalParameter(final FormalParameterContext ctx) { return this.processFormalParameter(ctx, ctx.variableModifiersOpt(), ctx.type(), ctx.ELLIPSIS(), ctx.variableDeclaratorId(), ctx.expression()); } @Override public Parameter visitThisFormalParameter(final ThisFormalParameterContext ctx) { return configureAST(new Parameter(this.visitType(ctx.type()), THIS_STR), ctx); } @Override public List visitClassOrInterfaceModifiersOpt(final ClassOrInterfaceModifiersOptContext ctx) { if (asBoolean(ctx.classOrInterfaceModifiers())) { return this.visitClassOrInterfaceModifiers(ctx.classOrInterfaceModifiers()); } return Collections.emptyList(); } @Override public List visitClassOrInterfaceModifiers(final ClassOrInterfaceModifiersContext ctx) { return ctx.classOrInterfaceModifier().stream() .map(this::visitClassOrInterfaceModifier) .collect(Collectors.toList()); } @Override public ModifierNode visitClassOrInterfaceModifier(final ClassOrInterfaceModifierContext ctx) { if (asBoolean(ctx.annotation())) { return configureAST(new ModifierNode(this.visitAnnotation(ctx.annotation()), ctx.getText()), ctx); } if (asBoolean(ctx.m)) { return configureAST(new ModifierNode(ctx.m.getType(), ctx.getText()), ctx); } throw createParsingFailedException("Unsupported class or interface modifier: " + ctx.getText(), ctx); } @Override public ModifierNode visitModifier(final ModifierContext ctx) { if (asBoolean(ctx.classOrInterfaceModifier())) { return configureAST(this.visitClassOrInterfaceModifier(ctx.classOrInterfaceModifier()), ctx); } if (asBoolean(ctx.m)) { return configureAST(new ModifierNode(ctx.m.getType(), ctx.getText()), ctx); } throw createParsingFailedException("Unsupported modifier: " + ctx.getText(), ctx); } @Override public List visitModifiers(final ModifiersContext ctx) { return ctx.modifier().stream() .map(this::visitModifier) .collect(Collectors.toList()); } @Override public List visitModifiersOpt(final ModifiersOptContext ctx) { if (asBoolean(ctx.modifiers())) { return this.visitModifiers(ctx.modifiers()); } return Collections.emptyList(); } @Override public ModifierNode visitVariableModifier(final VariableModifierContext ctx) { if (asBoolean(ctx.annotation())) { return configureAST(new ModifierNode(this.visitAnnotation(ctx.annotation()), ctx.getText()), ctx); } if (asBoolean(ctx.m)) { return configureAST(new ModifierNode(ctx.m.getType(), ctx.getText()), ctx); } throw createParsingFailedException("Unsupported variable modifier", ctx); } @Override public List visitVariableModifiersOpt(final VariableModifiersOptContext ctx) { if (asBoolean(ctx.variableModifiers())) { return this.visitVariableModifiers(ctx.variableModifiers()); } return Collections.emptyList(); } @Override public List visitVariableModifiers(final VariableModifiersContext ctx) { return ctx.variableModifier().stream() .map(this::visitVariableModifier) .collect(Collectors.toList()); } @Override public List> visitEmptyDims(final EmptyDimsContext ctx) { List> dimList = ctx.annotationsOpt().stream() .map(this::visitAnnotationsOpt) .collect(Collectors.toList()); Collections.reverse(dimList); return dimList; } @Override public List> visitEmptyDimsOpt(final EmptyDimsOptContext ctx) { if (!asBoolean(ctx.emptyDims())) { return Collections.emptyList(); } return this.visitEmptyDims(ctx.emptyDims()); } // type { ------------------------------------------------------------------ @Override public ClassNode visitType(final TypeContext ctx) { if (!asBoolean(ctx)) { return ClassHelper.dynamicType(); } ClassNode classNode = null; if (asBoolean(ctx.classOrInterfaceType())) { if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) ctx.classOrInterfaceType().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE); classNode = this.visitClassOrInterfaceType(ctx.classOrInterfaceType()); } else if (asBoolean(ctx.primitiveType())) { classNode = this.visitPrimitiveType(ctx.primitiveType()); } if (!asBoolean(classNode)) { if (VOID_STR.equals(ctx.getText())) { throw createParsingFailedException("void is not allowed here", ctx); } throw createParsingFailedException("Unsupported type: " + ctx.getText(), ctx); } classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); List> dimList = this.visitEmptyDimsOpt(ctx.emptyDimsOpt()); if (asBoolean(dimList)) { classNode = this.createArrayType(classNode, dimList); } return configureAST(classNode, ctx); } @Override public ClassNode visitClassOrInterfaceType(final ClassOrInterfaceTypeContext ctx) { ClassNode classNode; if (asBoolean(ctx.qualifiedClassName())) { if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) ctx.qualifiedClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE); classNode = this.visitQualifiedClassName(ctx.qualifiedClassName()); } else { if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) ctx.qualifiedStandardClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE); classNode = this.visitQualifiedStandardClassName(ctx.qualifiedStandardClassName()); } if (asBoolean(ctx.typeArguments())) { classNode.setGenericsTypes( this.visitTypeArguments(ctx.typeArguments())); } return configureAST(classNode, ctx); } @Override public GenericsType[] visitTypeArgumentsOrDiamond(final TypeArgumentsOrDiamondContext ctx) { if (asBoolean(ctx.typeArguments())) { return this.visitTypeArguments(ctx.typeArguments()); } if (asBoolean(ctx.LT())) { // e.g. <> return GenericsType.EMPTY_ARRAY; } throw createParsingFailedException("Unsupported type arguments or diamond: " + ctx.getText(), ctx); } @Override public GenericsType[] visitTypeArguments(final TypeArgumentsContext ctx) { return ctx.typeArgument().stream().map(this::visitTypeArgument).toArray(GenericsType[]::new); } @Override public GenericsType visitTypeArgument(final TypeArgumentContext ctx) { if (asBoolean(ctx.QUESTION())) { ClassNode baseType = configureAST(ClassHelper.makeWithoutCaching(QUESTION_STR), ctx.QUESTION()); baseType.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); if (!asBoolean(ctx.type())) { GenericsType genericsType = new GenericsType(baseType); genericsType.setWildcard(true); return configureAST(genericsType, ctx); } ClassNode[] upperBounds = null; ClassNode lowerBound = null; ClassNode classNode = this.visitType(ctx.type()); if (asBoolean(ctx.EXTENDS())) { upperBounds = new ClassNode[]{classNode}; } else if (asBoolean(ctx.SUPER())) { lowerBound = classNode; } GenericsType genericsType = new GenericsType(baseType, upperBounds, lowerBound); genericsType.setWildcard(true); return configureAST(genericsType, ctx); } else if (asBoolean(ctx.type())) { ClassNode baseType = this.visitType(ctx.type()); return configureAST(this.createGenericsType(baseType), ctx); } throw createParsingFailedException("Unsupported type argument: " + ctx.getText(), ctx); } @Override public ClassNode visitPrimitiveType(final PrimitiveTypeContext ctx) { return configureAST(ClassHelper.make(ctx.getText()).getPlainNodeReference(false), ctx); } // } type ------------------------------------------------------------------ @Override public VariableExpression visitVariableDeclaratorId(final VariableDeclaratorIdContext ctx) { return configureAST(new VariableExpression(this.visitIdentifier(ctx.identifier())), ctx); } @Override public TupleExpression visitVariableNames(final VariableNamesContext ctx) { return configureAST( new TupleExpression( ctx.variableDeclaratorId().stream() .map(this::visitVariableDeclaratorId) .collect(Collectors.toList()) ), ctx); } @Override public ClosureExpression visitClosureOrLambdaExpression(final ClosureOrLambdaExpressionContext ctx) { // GROOVY-8991: Difference in behaviour with closure and lambda if (asBoolean(ctx.closure())) { return configureAST(this.visitClosure(ctx.closure()), ctx); } else if (asBoolean(ctx.standardLambdaExpression())) { return configureAST(this.visitStandardLambdaExpression(ctx.standardLambdaExpression()), ctx); } // should never reach here throw createParsingFailedException("The node is not expected here" + ctx.getText(), ctx); } @Override public BlockStatement visitBlockStatementsOpt(final BlockStatementsOptContext ctx) { if (asBoolean(ctx.blockStatements())) { return configureAST(this.visitBlockStatements(ctx.blockStatements()), ctx); } return configureAST(this.createBlockStatement(), ctx); } @Override public BlockStatement visitBlockStatements(final BlockStatementsContext ctx) { return configureAST( this.createBlockStatement( ctx.blockStatement().stream() .map(this::visitBlockStatement) .filter(DefaultGroovyMethods::asBoolean) .collect(Collectors.toList())), ctx); } @Override public Statement visitBlockStatement(final BlockStatementContext ctx) { if (asBoolean(ctx.localVariableDeclaration())) { return configureAST(this.visitLocalVariableDeclaration(ctx.localVariableDeclaration()), ctx); } if (asBoolean(ctx.statement())) { Object astNode = this.visit(ctx.statement()); //this.configureAST((Statement) this.visit(ctx.statement()), ctx); if (null == astNode) { return null; } if (astNode instanceof Statement) { return (Statement) astNode; } else if (astNode instanceof MethodNode) { throw createParsingFailedException("Method definition not expected here", ctx); } else if (astNode instanceof ImportNode) { throw createParsingFailedException("Import statement not expected here", ctx); } else { throw createParsingFailedException("The statement(" + astNode.getClass() + ") not expected here", ctx); } } throw createParsingFailedException("Unsupported block statement: " + ctx.getText(), ctx); } @Override public List visitAnnotationsOpt(final AnnotationsOptContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } return ctx.annotation().stream() .map(this::visitAnnotation) .collect(Collectors.toList()); } @Override public AnnotationNode visitAnnotation(final AnnotationContext ctx) { String annotationName = this.visitAnnotationName(ctx.annotationName()); AnnotationNode annotationNode = new AnnotationNode(makeClassNode(annotationName)); List> annotationElementValues = this.visitElementValues(ctx.elementValues()); annotationElementValues.forEach(e -> { Expression v2 = e.getV2(); if (v2 instanceof MethodPointerExpression) { v2 = closureX(Parameter.EMPTY_ARRAY, block(stmt(callThisX("withMethodClosure", v2)))); } annotationNode.addMember(e.getV1(), v2); }); configureAST(annotationNode.getClassNode(), ctx.annotationName()); return configureAST(annotationNode, ctx); } @Override public List> visitElementValues(final ElementValuesContext ctx) { if (!asBoolean(ctx)) { return Collections.emptyList(); } List> annotationElementValues = new LinkedList<>(); if (asBoolean(ctx.elementValuePairs())) { this.visitElementValuePairs(ctx.elementValuePairs()).forEach((key, value) -> annotationElementValues.add(tuple(key, value))); } else if (asBoolean(ctx.elementValue())) { annotationElementValues.add(tuple(VALUE_STR, this.visitElementValue(ctx.elementValue()))); } return annotationElementValues; } @Override public String visitAnnotationName(final AnnotationNameContext ctx) { return this.visitQualifiedClassName(ctx.qualifiedClassName()).getName(); } @Override public Map visitElementValuePairs(final ElementValuePairsContext ctx) { return ctx.elementValuePair().stream() .map(this::visitElementValuePair) .collect(Collectors.toMap( Tuple2::getV1, Tuple2::getV2, (k, v) -> { throw new IllegalStateException(String.format("Duplicate key %s", k)); }, LinkedHashMap::new )); } @Override public Tuple2 visitElementValuePair(final ElementValuePairContext ctx) { return tuple(ctx.elementValuePairName().getText(), this.visitElementValue(ctx.elementValue())); } @Override public Expression visitElementValue(final ElementValueContext ctx) { if (asBoolean(ctx.expression())) { return configureAST((Expression) this.visit(ctx.expression()), ctx); } if (asBoolean(ctx.annotation())) { return configureAST(new AnnotationConstantExpression(this.visitAnnotation(ctx.annotation())), ctx); } if (asBoolean(ctx.elementValueArrayInitializer())) { return configureAST(this.visitElementValueArrayInitializer(ctx.elementValueArrayInitializer()), ctx); } throw createParsingFailedException("Unsupported element value: " + ctx.getText(), ctx); } @Override public ListExpression visitElementValueArrayInitializer(final ElementValueArrayInitializerContext ctx) { return configureAST(new ListExpression(ctx.elementValue().stream().map(this::visitElementValue).collect(Collectors.toList())), ctx); } @Override public String visitClassName(final ClassNameContext ctx) { return ctx.getText(); } @Override public String visitIdentifier(final IdentifierContext ctx) { return ctx.getText(); } @Override public String visitQualifiedName(final QualifiedNameContext ctx) { return ctx.qualifiedNameElement().stream() .map(ParseTree::getText) .collect(Collectors.joining(DOT_STR)); } @Override public ClassNode visitAnnotatedQualifiedClassName(final AnnotatedQualifiedClassNameContext ctx) { ClassNode classNode = this.visitQualifiedClassName(ctx.qualifiedClassName()); classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); return classNode; } @Override public ClassNode[] visitQualifiedClassNameList(final QualifiedClassNameListContext ctx) { if (!asBoolean(ctx)) { return ClassNode.EMPTY_ARRAY; } return ctx.annotatedQualifiedClassName().stream() .map(this::visitAnnotatedQualifiedClassName) .toArray(ClassNode[]::new); } @Override public ClassNode visitQualifiedClassName(final QualifiedClassNameContext ctx) { return this.createClassNode(ctx); } @Override public ClassNode visitQualifiedStandardClassName(final QualifiedStandardClassNameContext ctx) { return this.createClassNode(ctx); } private ClassNode createArrayType(final ClassNode elementType, List> dimAnnotationsList) { ClassNode arrayType = elementType; for (int i = dimAnnotationsList.size() - 1; i >= 0; i -= 1) { arrayType = this.createArrayType(arrayType); arrayType.addAnnotations(dimAnnotationsList.get(i)); } return arrayType; } private ClassNode createArrayType(final ClassNode elementType) { if (ClassHelper.isPrimitiveVoid(elementType)) { throw this.createParsingFailedException("void[] is an invalid type", elementType); } return elementType.makeArray(); } private ClassNode createClassNode(final GroovyParserRuleContext ctx) { ClassNode result = makeClassNode(ctx.getText()); if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) { // type in the "instanceof" expression shouldn't have redirect } else { result = this.proxyClassNode(result); } return configureAST(result, ctx); } private ClassNode proxyClassNode(final ClassNode classNode) { if (!classNode.isUsingGenerics()) { return classNode; } ClassNode cn = ClassHelper.makeWithoutCaching(classNode.getName()); cn.setRedirect(classNode); return cn; } /** * Visit tree safely, no NPE occurred when the tree is null. * * @param tree an AST node * @return the visiting result */ @Override public Object visit(final ParseTree tree) { if (!asBoolean(tree)) { return null; } return super.visit(tree); } // e.g. obj.a(1, 2) or obj.a 1, 2 private MethodCallExpression createMethodCallExpression(final PropertyExpression propertyExpression, final Expression arguments) { MethodCallExpression methodCallExpression = new MethodCallExpression( propertyExpression.getObjectExpression(), propertyExpression.getProperty(), arguments ); methodCallExpression.setImplicitThis(false); methodCallExpression.setSafe(propertyExpression.isSafe()); methodCallExpression.setSpreadSafe(propertyExpression.isSpreadSafe()); // method call obj*.m(): "safe"(false) and "spreadSafe"(true) // property access obj*.p: "safe"(true) and "spreadSafe"(true) // so we have to reset safe here. if (propertyExpression.isSpreadSafe()) { methodCallExpression.setSafe(false); } // if the generics types metadata is not empty, it is a generic method call, e.g. obj.a(1, 2) methodCallExpression.setGenericsTypes( propertyExpression.getNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES)); return methodCallExpression; } // e.g. m(1, 2) or m 1, 2 private MethodCallExpression createMethodCallExpression(final Expression baseExpr, final Expression arguments) { Expression thisExpr = new VariableExpression("this"); thisExpr.setColumnNumber(baseExpr.getColumnNumber()); thisExpr.setLineNumber(baseExpr.getLineNumber()); return new MethodCallExpression( thisExpr, (baseExpr instanceof VariableExpression) ? this.createConstantExpression(baseExpr) : baseExpr, arguments ); } private Parameter processFormalParameter(final GroovyParserRuleContext ctx, final VariableModifiersOptContext variableModifiersOptContext, final TypeContext typeContext, final TerminalNode ellipsis, final VariableDeclaratorIdContext variableDeclaratorIdContext, final ExpressionContext expressionContext) { ClassNode classNode = this.visitType(typeContext); if (asBoolean(ellipsis)) { classNode = this.createArrayType(classNode); if (!asBoolean(typeContext)) { configureAST(classNode, ellipsis); } else { configureAST(classNode, typeContext, configureAST(new ConstantExpression("..."), ellipsis)); } } ModifierManager modifierManager = new ModifierManager(this, this.visitVariableModifiersOpt(variableModifiersOptContext)); Parameter parameter = modifierManager .processParameter( configureAST( new Parameter( classNode, this.visitVariableDeclaratorId(variableDeclaratorIdContext).getName() ), ctx ) ); parameter.putNodeMetaData(PARAMETER_MODIFIER_MANAGER, modifierManager); parameter.putNodeMetaData(PARAMETER_CONTEXT, ctx); if (asBoolean(expressionContext)) { parameter.setInitialExpression((Expression) this.visit(expressionContext)); } return parameter; } private Expression createPathExpression(final Expression primaryExpr, final List pathElementContextList) { return (Expression) pathElementContextList.stream() .map(e -> (Object) e) .reduce(primaryExpr, (r, e) -> { PathElementContext pathElementContext = (PathElementContext) e; pathElementContext.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR, r); Expression expression = this.visitPathElement(pathElementContext); if (isTrue((Expression) r, PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN)) { expression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN, Boolean.TRUE); } return expression; } ); } private GenericsType createGenericsType(final ClassNode classNode) { return configureAST(new GenericsType(classNode), classNode); } private ConstantExpression createConstantExpression(final Expression expression) { if (expression instanceof ConstantExpression) { return (ConstantExpression) expression; } return configureAST(new ConstantExpression(expression.getText()), expression); } private BinaryExpression createBinaryExpression(final ExpressionContext left, final Token op, final ExpressionContext right) { return new BinaryExpression((Expression) this.visit(left), this.createGroovyToken(op), (Expression) this.visit(right)); } private BinaryExpression createBinaryExpression(final ExpressionContext left, final Token op, final ExpressionContext right, final ExpressionContext ctx) { BinaryExpression binaryExpression = this.createBinaryExpression(left, op, right); if (isTrue(ctx, IS_INSIDE_CONDITIONAL_EXPRESSION)) { return configureAST(binaryExpression, op); } return configureAST(binaryExpression, ctx); } private Statement unpackStatement(final Statement statement) { if (statement instanceof DeclarationListStatement) { List expressionStatementList = ((DeclarationListStatement) statement).getDeclarationStatements(); if (1 == expressionStatementList.size()) { return expressionStatementList.get(0); } return configureAST(this.createBlockStatement(statement), statement); // if DeclarationListStatement contains more than 1 declarations, maybe it's better to create a block to hold them } return statement; } BlockStatement createBlockStatement(final Statement... statements) { return this.createBlockStatement(Arrays.asList(statements)); } private BlockStatement createBlockStatement(final List statementList) { return this.appendStatementsToBlockStatement(new BlockStatement(), statementList); } public BlockStatement appendStatementsToBlockStatement(final BlockStatement bs, final Statement... statements) { return this.appendStatementsToBlockStatement(bs, Arrays.asList(statements)); } private BlockStatement appendStatementsToBlockStatement(final BlockStatement bs, final List statementList) { return (BlockStatement) statementList.stream() .reduce(bs, (r, e) -> { BlockStatement blockStatement = (BlockStatement) r; if (e instanceof DeclarationListStatement) { ((DeclarationListStatement) e).getDeclarationStatements().forEach(blockStatement::addStatement); } else { blockStatement.addStatement(e); } return blockStatement; }); } private boolean isAnnotationDeclaration(final ClassNode classNode) { return asBoolean(classNode) && classNode.isAnnotationDefinition(); } private boolean isSyntheticPublic(final boolean isAnnotationDeclaration, final boolean isAnonymousInnerEnumDeclaration, final boolean hasReturnType, final ModifierManager modifierManager) { if (modifierManager.containsVisibilityModifier()) { return false; } if (isAnnotationDeclaration) { return true; } if (hasReturnType && (modifierManager.containsAny(DEF, VAR))) { return true; } if (!hasReturnType || modifierManager.containsNonVisibilityModifier() || modifierManager.containsAnnotations()) { return true; } return isAnonymousInnerEnumDeclaration; } // the mixins of interface and annotation should be null private void hackMixins(final ClassNode classNode) { classNode.setMixins(null); } private static final Map TYPE_DEFAULT_VALUE_MAP = Maps.of( ClassHelper.int_TYPE, 0, ClassHelper.long_TYPE, 0L, ClassHelper.double_TYPE, 0.0D, ClassHelper.float_TYPE, 0.0F, ClassHelper.short_TYPE, (short) 0, ClassHelper.byte_TYPE, (byte) 0, ClassHelper.char_TYPE, (char) 0, ClassHelper.boolean_TYPE, Boolean.FALSE ); private Object findDefaultValueByType(final ClassNode type) { return TYPE_DEFAULT_VALUE_MAP.get(type); } private boolean isPackageInfoDeclaration() { String name = this.sourceUnit.getName(); return name != null && name.endsWith(PACKAGE_INFO_FILE_NAME); } private boolean isBlankScript() { return moduleNode.getStatementBlock().isEmpty() && moduleNode.getMethods().isEmpty() && moduleNode.getClasses().isEmpty(); } private boolean isInsideParentheses(final NodeMetaDataHandler nodeMetaDataHandler) { Number insideParenLevel = nodeMetaDataHandler.getNodeMetaData(INSIDE_PARENTHESES_LEVEL); return insideParenLevel != null && insideParenLevel.intValue() > 0; } private boolean isBuiltInType(final Expression expression) { return (expression instanceof VariableExpression && isTrue(expression, IS_BUILT_IN_TYPE)); } private org.codehaus.groovy.syntax.Token createGroovyTokenByType(final Token token, final int type) { if (token == null) { throw new IllegalArgumentException("token should not be null"); } return new org.codehaus.groovy.syntax.Token(type, token.getText(), token.getLine(), token.getCharPositionInLine()); } private org.codehaus.groovy.syntax.Token createGroovyToken(final Token token) { return this.createGroovyToken(token, 1); } private org.codehaus.groovy.syntax.Token createGroovyToken(final Token token, final int cardinality) { String tokenText = token.getText(); int tokenType = token.getType(); String text = 1 == cardinality ? tokenText : StringGroovyMethods.multiply(tokenText, cardinality); return new org.codehaus.groovy.syntax.Token( RANGE_EXCLUSIVE_FULL == tokenType || RANGE_EXCLUSIVE_LEFT == tokenType || RANGE_EXCLUSIVE_RIGHT == tokenType || RANGE_INCLUSIVE == tokenType ? Types.RANGE_OPERATOR : SAFE_INDEX == tokenType ? Types.LEFT_SQUARE_BRACKET : Types.lookup(text, Types.ANY), text, token.getLine(), token.getCharPositionInLine() + 1 ); } /** * Sets the script source position. */ private void configureScriptClassNode() { var scriptClassNode = moduleNode.getScriptClassDummy(); if (scriptClassNode != null) { List statements = moduleNode.getStatementBlock().getStatements(); if (!statements.isEmpty()) { Statement firstStatement = statements.get(0); scriptClassNode.setSourcePosition(firstStatement); Statement lastStatement = statements.get(statements.size() - 1); scriptClassNode.setLastLineNumber(lastStatement.getLastLineNumber()); scriptClassNode.setLastColumnNumber(lastStatement.getLastColumnNumber()); } } } private String getOriginalText(final ParserRuleContext context) { return lexer.getInputStream().getText(Interval.of(context.getStart().getStartIndex(), context.getStop().getStopIndex())); } private static boolean isTrue(final NodeMetaDataHandler obj, final String key) { return Boolean.TRUE.equals(obj.getNodeMetaData(key)); } private CompilationFailedException createParsingFailedException(final String msg, final GroovyParserRuleContext ctx) { return createParsingFailedException( new SyntaxException(msg, ctx.start.getLine(), ctx.start.getCharPositionInLine() + 1, ctx.stop.getLine(), ctx.stop.getCharPositionInLine() + 1 + ctx.stop.getText().length())); } CompilationFailedException createParsingFailedException(final String msg, final Tuple2 start, final Tuple2 end) { return createParsingFailedException( new SyntaxException(msg, start.getV1(), start.getV2(), end.getV1(), end.getV2())); } CompilationFailedException createParsingFailedException(final String msg, final ASTNode node) { Objects.requireNonNull(node, "node passed into createParsingFailedException should not be null"); return createParsingFailedException( new SyntaxException(msg, node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber())); } private CompilationFailedException createParsingFailedException(final String msg, final TerminalNode node) { return createParsingFailedException(msg, node.getSymbol()); } private CompilationFailedException createParsingFailedException(final String msg, final Token token) { return createParsingFailedException( new SyntaxException(msg, token.getLine(), token.getCharPositionInLine() + 1, token.getLine(), token.getCharPositionInLine() + 1 + token.getText().length())); } private CompilationFailedException createParsingFailedException(final Throwable t) { if (t instanceof SyntaxException) { this.collectSyntaxError((SyntaxException) t); } else if (t instanceof GroovySyntaxError) { GroovySyntaxError groovySyntaxError = (GroovySyntaxError) t; this.collectSyntaxError( new SyntaxException( groovySyntaxError.getMessage(), groovySyntaxError, groovySyntaxError.getLine(), groovySyntaxError.getColumn())); } else if (t instanceof Exception) { this.collectException((Exception) t); } return new CompilationFailedException( CompilePhase.PARSING.getPhaseNumber(), this.sourceUnit, t); } private void collectSyntaxError(final SyntaxException e) { sourceUnit.getErrorCollector().addFatalError(new SyntaxErrorMessage(e, sourceUnit)); } private void collectException(final Exception e) { sourceUnit.getErrorCollector().addException(e, this.sourceUnit); } private ANTLRErrorListener createANTLRErrorListener() { return new ANTLRErrorListener() { @Override public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, final int charPositionInLine, final String msg, final RecognitionException e) { collectSyntaxError(new SyntaxException(msg, line, charPositionInLine + 1)); } }; } private void removeErrorListeners() { lexer.removeErrorListeners(); parser.removeErrorListeners(); } private void addErrorListeners() { lexer.removeErrorListeners(); lexer.addErrorListener(this.createANTLRErrorListener()); parser.removeErrorListeners(); parser.addErrorListener(this.createANTLRErrorListener()); } //-------------------------------------------------------------------------- private static class DeclarationListStatement extends Statement { private final List declarationStatements; public DeclarationListStatement(DeclarationExpression... declarations) { this(Arrays.asList(declarations)); } public DeclarationListStatement(List declarations) { this.declarationStatements = declarations.stream() .map(e -> configureAST(new ExpressionStatement(e), e)) .collect(Collectors.toList()); } public List getDeclarationStatements() { List declarationListStatementLabels = this.getStatementLabels(); this.declarationStatements.forEach(e -> { if (null != declarationListStatementLabels) { // clear existing statement labels before setting labels if (null != e.getStatementLabels()) { e.getStatementLabels().clear(); } declarationListStatementLabels.forEach(e::addStatementLabel); } }); return this.declarationStatements; } public List getDeclarationExpressions() { return this.declarationStatements.stream() .map(e -> (DeclarationExpression) e.getExpression()) .collect(Collectors.toList()); } } private final ModuleNode moduleNode; private final SourceUnit sourceUnit; private final GroovyLangLexer lexer; private final GroovyLangParser parser; private final GroovydocManager groovydocManager; private final TryWithResourcesASTTransformation tryWithResourcesASTTransformation; private final List classNodeList = new ArrayList<>(); private final Deque classNodeStack = new ArrayDeque<>(); private final Deque> anonymousInnerClassesDefinedInMethodStack = new ArrayDeque<>(); private final Deque switchExpressionRuleContextStack = new ArrayDeque<>(); private Tuple2 numberFormatError; private int visitingClosureCount; private int visitingLoopStatementCount; private int visitingSwitchStatementCount; private int visitingAssertStatementCount; private int visitingArrayInitializerCount; private static final int SLL_THRESHOLD = SystemUtil.getIntegerSafe("groovy.antlr4.sll.threshold", -1); private static final String QUESTION_STR = "?"; private static final String DOT_STR = "."; private static final String SUB_STR = "-"; private static final String ASSIGN_STR = "="; private static final String VALUE_STR = "value"; private static final String DOLLAR_STR = "$"; private static final String CALL_STR = "call"; private static final String THIS_STR = "this"; private static final String SUPER_STR = "super"; private static final String VOID_STR = "void"; private static final String SLASH_STR = "/"; private static final String SLASH_DOLLAR_STR = "/$"; private static final String TDQ_STR = "\"\"\""; private static final String TSQ_STR = "'''"; private static final String SQ_STR = "'"; private static final String DQ_STR = "\""; private static final String DOLLAR_SLASH_STR = "$/"; private static final Map QUOTATION_MAP = Maps.of( DQ_STR, DQ_STR, SQ_STR, SQ_STR, TDQ_STR, TDQ_STR, TSQ_STR, TSQ_STR, SLASH_STR, SLASH_STR, DOLLAR_SLASH_STR, SLASH_DOLLAR_STR ); private static final String PACKAGE_INFO = "package-info"; private static final String PACKAGE_INFO_FILE_NAME = PACKAGE_INFO + ".groovy"; private static final String CLASS_NAME = "_CLASS_NAME"; private static final String INSIDE_PARENTHESES_LEVEL = "_INSIDE_PARENTHESES_LEVEL"; private static final String IS_INSIDE_INSTANCEOF_EXPR = "_IS_INSIDE_INSTANCEOF_EXPR"; private static final String IS_SWITCH_DEFAULT = "_IS_SWITCH_DEFAULT"; private static final String IS_NUMERIC = "_IS_NUMERIC"; private static final String IS_STRING = "_IS_STRING"; private static final String IS_INTERFACE_WITH_DEFAULT_METHODS = "_IS_INTERFACE_WITH_DEFAULT_METHODS"; private static final String IS_INSIDE_CONDITIONAL_EXPRESSION = "_IS_INSIDE_CONDITIONAL_EXPRESSION"; private static final String IS_COMMAND_EXPRESSION = "_IS_COMMAND_EXPRESSION"; private static final String IS_BUILT_IN_TYPE = "_IS_BUILT_IN_TYPE"; private static final String PATH_EXPRESSION_BASE_EXPR = "_PATH_EXPRESSION_BASE_EXPR"; private static final String PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES = "_PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES"; private static final String PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN = "_PATH_EXPRESSION_BASE_EXPR_SAFE_CHAIN"; private static final String CMD_EXPRESSION_BASE_EXPR = "_CMD_EXPRESSION_BASE_EXPR"; private static final String TYPE_DECLARATION_MODIFIERS = "_TYPE_DECLARATION_MODIFIERS"; private static final String COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS = "_COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS"; private static final String CLASS_DECLARATION_CLASS_NODE = "_CLASS_DECLARATION_CLASS_NODE"; private static final String VARIABLE_DECLARATION_VARIABLE_TYPE = "_VARIABLE_DECLARATION_VARIABLE_TYPE"; private static final String ANONYMOUS_INNER_CLASS_SUPER_CLASS = "_ANONYMOUS_INNER_CLASS_SUPER_CLASS"; 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 IS_YIELD_STATEMENT = "_IS_YIELD_STATEMENT"; private static final String PARAMETER_MODIFIER_MANAGER = "_PARAMETER_MODIFIER_MANAGER"; private static final String PARAMETER_CONTEXT = "_PARAMETER_CONTEXT"; private static final String IS_RECORD_GENERATED = "_IS_RECORD_GENERATED"; private static final String RECORD_HEADER = "_RECORD_HEADER"; private static final String RECORD_TYPE_NAME = "groovy.transform.RecordType"; }