You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2017/05/08 00:27:07 UTC
[07/17] groovy git commit: rename antlr4 parser to remove groovy-
prefix since that is by convention for modules
http://git-wip-us.apache.org/repos/asf/groovy/blob/73acbcfe/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
----------------------------------------------------------------------
diff --git a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
new file mode 100644
index 0000000..e0708f7
--- /dev/null
+++ b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -0,0 +1,4317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.parser.antlr4;
+
+import groovy.lang.IntRange;
+import org.antlr.v4.runtime.ANTLRErrorListener;
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.atn.PredictionMode;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.groovy.parser.antlr4.internal.AtnManager;
+import org.apache.groovy.parser.antlr4.internal.DescriptiveErrorStrategy;
+import org.apache.groovy.parser.antlr4.util.StringUtils;
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.antlr.EnumHelper;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+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.ModuleNode;
+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.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+import org.codehaus.groovy.runtime.StringGroovyMethods;
+import org.codehaus.groovy.syntax.Numbers;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.*;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;
+
+/**
+ * Building the AST from the parse tree generated by Antlr4
+ *
+ * @author <a href="mailto:realbluesun@hotmail.com">Daniel.Sun</a>
+ * Created on 2016/08/14
+ */
+public class AstBuilder extends GroovyParserBaseVisitor<Object> implements GroovyParserVisitor<Object> {
+
+ public AstBuilder(SourceUnit sourceUnit, ClassLoader classLoader) {
+ this.sourceUnit = sourceUnit;
+ this.moduleNode = new ModuleNode(sourceUnit);
+ this.classLoader = classLoader; // unused for the time being
+
+ this.lexer = new GroovyLangLexer(
+ new ANTLRInputStream(
+ this.readSourceCode(sourceUnit)));
+ this.parser = new GroovyLangParser(
+ new CommonTokenStream(this.lexer));
+
+ this.parser.setErrorHandler(new DescriptiveErrorStrategy());
+
+ this.tryWithResourcesASTTransformation = new TryWithResourcesASTTransformation(this);
+ this.groovydocManager = new GroovydocManager(this);
+ }
+
+ private GroovyParserRuleContext buildCST() {
+ GroovyParserRuleContext result;
+
+ // parsing have to wait util clearing is complete.
+ AtnManager.RRWL.readLock().lock();
+ 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;
+ }
+
+ result = buildCST(PredictionMode.LL);
+ } finally {
+ AtnManager.RRWL.readLock().unlock();
+ }
+
+ return result;
+ }
+
+ private GroovyParserRuleContext buildCST(PredictionMode predictionMode) {
+ parser.getInterpreter().setPredictionMode(predictionMode);
+
+ if (PredictionMode.SLL.equals(predictionMode)) {
+ this.removeErrorListeners();
+ } else {
+ ((CommonTokenStream) parser.getInputStream()).reset();
+ this.addErrorListeners();
+ }
+
+ return parser.compilationUnit();
+ }
+
+ public ModuleNode buildAST() {
+ try {
+ return (ModuleNode) this.visit(this.buildCST());
+ } catch (Throwable t) {
+ CompilationFailedException cfe;
+
+ if (t instanceof CompilationFailedException) {
+ cfe = (CompilationFailedException) t;
+ } else if (t instanceof ParseCancellationException) {
+ cfe = createParsingFailedException(t.getCause());
+ } else {
+ cfe = createParsingFailedException(t);
+ }
+
+// LOGGER.log(Level.SEVERE, "Failed to build AST", cfe);
+
+ throw cfe;
+ }
+ }
+
+ @Override
+ public ModuleNode visitCompilationUnit(CompilationUnitContext ctx) {
+ this.visit(ctx.packageDeclaration());
+
+ ctx.statement().stream()
+ .map(this::visit)
+// .filter(e -> e instanceof Statement)
+ .forEach(e -> {
+ if (e instanceof DeclarationListStatement) { // local variable declaration
+ ((DeclarationListStatement) e).getDeclarationStatements().forEach(moduleNode::addStatement);
+ } else if (e instanceof Statement) {
+ moduleNode.addStatement((Statement) e);
+ } else if (e instanceof MethodNode) { // script method
+ moduleNode.addMethod((MethodNode) e);
+ }
+ });
+
+ this.classNodeList.forEach(moduleNode::addClass);
+
+ if (this.isPackageInfoDeclaration()) {
+ this.addPackageInfoClassNode();
+ } else {
+ // if groovy source file only contains blank(including EOF), add "return null" to the AST
+ if (this.isBlankScript(ctx)) {
+ this.addEmptyReturnStatement();
+ }
+ }
+
+ this.configureScriptClassNode();
+
+ return moduleNode;
+ }
+
+ @Override
+ public PackageNode visitPackageDeclaration(PackageDeclarationContext ctx) {
+ String packageName = this.visitQualifiedName(ctx.qualifiedName());
+ moduleNode.setPackageName(packageName + DOT_STR);
+
+ PackageNode packageNode = moduleNode.getPackage();
+
+ this.visitAnnotationsOpt(ctx.annotationsOpt()).forEach(packageNode::addAnnotation);
+
+ return this.configureAST(packageNode, ctx);
+ }
+
+ @Override
+ public ImportNode visitImportDeclaration(ImportDeclarationContext ctx) {
+ ImportNode importNode;
+
+ boolean hasStatic = asBoolean(ctx.STATIC());
+ boolean hasStar = asBoolean(ctx.MUL());
+ boolean hasAlias = asBoolean(ctx.alias);
+
+ List<AnnotationNode> annotationNodeList = this.visitAnnotationsOpt(ctx.annotationsOpt());
+
+ if (hasStatic) {
+ if (hasStar) { // e.g. import static java.lang.Math.*
+ String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
+ ClassNode type = ClassHelper.make(qualifiedName);
+
+
+ moduleNode.addStaticStarImport(type.getText(), type, annotationNodeList);
+
+ importNode = last(moduleNode.getStaticStarImports().values());
+ } else { // e.g. import static java.lang.Math.pow
+ List<GroovyParserRuleContext> identifierList = new LinkedList<>(ctx.qualifiedName().qualifiedNameElement());
+ int identifierListSize = identifierList.size();
+ String name = identifierList.get(identifierListSize - 1).getText();
+ ClassNode classNode =
+ ClassHelper.make(
+ identifierList.stream()
+ .limit(identifierListSize - 1)
+ .map(ParseTree::getText)
+ .collect(Collectors.joining(DOT_STR)));
+ String alias = hasAlias
+ ? ctx.alias.getText()
+ : name;
+
+ moduleNode.addStaticImport(classNode, name, alias, annotationNodeList);
+
+ importNode = last(moduleNode.getStaticImports().values());
+ }
+ } else {
+ if (hasStar) { // e.g. import java.util.*
+ String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
+
+ moduleNode.addStarImport(qualifiedName + DOT_STR, annotationNodeList);
+
+ importNode = last(moduleNode.getStarImports());
+ } else { // e.g. import java.util.Map
+ String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
+ String name = last(ctx.qualifiedName().qualifiedNameElement()).getText();
+ ClassNode classNode = ClassHelper.make(qualifiedName);
+ String alias = hasAlias
+ ? ctx.alias.getText()
+ : name;
+
+ moduleNode.addImport(alias, classNode, annotationNodeList);
+
+ importNode = last(moduleNode.getImports());
+ }
+ }
+
+ return this.configureAST(importNode, ctx);
+ }
+
+ // statement { --------------------------------------------------------------------
+ @Override
+ public AssertStatement visitAssertStatement(AssertStatementContext ctx) {
+ Expression conditionExpression = (Expression) this.visit(ctx.ce);
+ BooleanExpression booleanExpression =
+ this.configureAST(
+ new BooleanExpression(conditionExpression), conditionExpression);
+
+ if (!asBoolean(ctx.me)) {
+ return this.configureAST(
+ new AssertStatement(booleanExpression), ctx);
+ }
+
+ return this.configureAST(new AssertStatement(booleanExpression,
+ (Expression) this.visit(ctx.me)),
+ ctx);
+ }
+
+ @Override
+ public AssertStatement visitAssertStmtAlt(AssertStmtAltContext ctx) {
+ return this.configureAST(this.visitAssertStatement(ctx.assertStatement()), ctx);
+ }
+
+ @Override
+ public IfStatement visitIfElseStmtAlt(IfElseStmtAltContext ctx) {
+ Expression conditionExpression = this.visitParExpression(ctx.parExpression());
+ BooleanExpression booleanExpression =
+ this.configureAST(
+ new BooleanExpression(conditionExpression), conditionExpression);
+
+ Statement ifBlock =
+ this.unpackStatement(
+ (Statement) this.visit(ctx.tb));
+ Statement elseBlock =
+ this.unpackStatement(
+ asBoolean(ctx.ELSE())
+ ? (Statement) this.visit(ctx.fb)
+ : EmptyStatement.INSTANCE);
+
+ return this.configureAST(new IfStatement(booleanExpression, ifBlock, elseBlock), ctx);
+ }
+
+ @Override
+ public Statement visitLoopStmtAlt(LoopStmtAltContext ctx) {
+ return this.configureAST((Statement) this.visit(ctx.loopStatement()), ctx);
+ }
+
+ @Override
+ public ForStatement visitForStmtAlt(ForStmtAltContext ctx) {
+ Pair<Parameter, Expression> controlPair = this.visitForControl(ctx.forControl());
+
+ Statement loopBlock = this.unpackStatement((Statement) this.visit(ctx.statement()));
+
+ return this.configureAST(
+ new ForStatement(controlPair.getKey(), controlPair.getValue(), asBoolean(loopBlock) ? loopBlock : EmptyStatement.INSTANCE),
+ ctx);
+ }
+
+ @Override
+ public Pair<Parameter, Expression> visitForControl(ForControlContext ctx) {
+ if (asBoolean(ctx.enhancedForControl())) { // e.g. for(int i in 0..<10) {}
+ return this.visitEnhancedForControl(ctx.enhancedForControl());
+ }
+
+ if (asBoolean(ctx.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(ForInitContext ctx) {
+ if (!asBoolean(ctx)) {
+ return EmptyExpression.INSTANCE;
+ }
+
+ if (asBoolean(ctx.localVariableDeclaration())) {
+ DeclarationListStatement declarationListStatement = this.visitLocalVariableDeclaration(ctx.localVariableDeclaration());
+ List<?> declarationExpressionList = declarationListStatement.getDeclarationExpressions();
+
+ if (declarationExpressionList.size() == 1) {
+ return this.configureAST((Expression) declarationExpressionList.get(0), ctx);
+ } else {
+ return this.configureAST(new ClosureListExpression((List<Expression>) declarationExpressionList), ctx);
+ }
+ }
+
+ if (asBoolean(ctx.expressionList())) {
+ return this.translateExpressionList(ctx.expressionList());
+ }
+
+ throw createParsingFailedException("Unsupported for init: " + ctx.getText(), ctx);
+ }
+
+ @Override
+ public Expression visitForUpdate(ForUpdateContext ctx) {
+ if (!asBoolean(ctx)) {
+ return EmptyExpression.INSTANCE;
+ }
+
+ return this.translateExpressionList(ctx.expressionList());
+ }
+
+ private Expression translateExpressionList(ExpressionListContext ctx) {
+ List<Expression> expressionList = this.visitExpressionList(ctx);
+
+ if (expressionList.size() == 1) {
+ return this.configureAST(expressionList.get(0), ctx);
+ } else {
+ return this.configureAST(new ClosureListExpression(expressionList), ctx);
+ }
+ }
+
+ @Override
+ public Pair<Parameter, Expression> visitEnhancedForControl(EnhancedForControlContext ctx) {
+ Parameter parameter = this.configureAST(
+ new Parameter(this.visitType(ctx.type()), this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName()),
+ ctx.variableDeclaratorId());
+
+ // FIXME Groovy will ignore variableModifier of parameter in the for control
+ // In order to make the new parser behave same with the old one, we do not process variableModifier*
+
+ return new Pair<>(parameter, (Expression) this.visit(ctx.expression()));
+ }
+
+ @Override
+ public Pair<Parameter, Expression> visitClassicalForControl(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 new Pair<>(ForStatement.FOR_LOOP_DUMMY, closureListExpression);
+ }
+
+ @Override
+ public WhileStatement visitWhileStmtAlt(WhileStmtAltContext ctx) {
+ Expression conditionExpression = this.visitParExpression(ctx.parExpression());
+ BooleanExpression booleanExpression =
+ this.configureAST(
+ new BooleanExpression(conditionExpression), conditionExpression);
+
+ Statement loopBlock = this.unpackStatement((Statement) this.visit(ctx.statement()));
+
+ return this.configureAST(
+ new WhileStatement(booleanExpression, asBoolean(loopBlock) ? loopBlock : EmptyStatement.INSTANCE),
+ ctx);
+ }
+
+ @Override
+ public DoWhileStatement visitDoWhileStmtAlt(DoWhileStmtAltContext ctx) {
+ Expression conditionExpression = this.visitParExpression(ctx.parExpression());
+
+ BooleanExpression booleanExpression =
+ this.configureAST(
+ new BooleanExpression(conditionExpression),
+ conditionExpression
+ );
+
+ Statement loopBlock = this.unpackStatement((Statement) this.visit(ctx.statement()));
+
+ return this.configureAST(
+ new DoWhileStatement(booleanExpression, asBoolean(loopBlock) ? loopBlock : EmptyStatement.INSTANCE),
+ ctx);
+ }
+
+ @Override
+ public Statement visitTryCatchStmtAlt(TryCatchStmtAltContext ctx) {
+ return this.configureAST(this.visitTryCatchStatement(ctx.tryCatchStatement()), ctx);
+ }
+
+ @Override
+ public Statement visitTryCatchStatement(TryCatchStatementContext ctx) {
+ TryCatchStatement tryCatchStatement =
+ new TryCatchStatement((Statement) this.visit(ctx.block()),
+ this.visitFinallyBlock(ctx.finallyBlock()));
+
+ if (asBoolean(ctx.resources())) {
+ this.visitResources(ctx.resources()).forEach(tryCatchStatement::addResource);
+ }
+
+ ctx.catchClause().stream().map(this::visitCatchClause)
+ .reduce(new LinkedList<CatchStatement>(), (r, e) -> {
+ r.addAll(e); // merge several LinkedList<CatchStatement> instances into one LinkedList<CatchStatement> instance
+ return r;
+ })
+ .forEach(tryCatchStatement::addCatch);
+
+ return this.configureAST(
+ tryWithResourcesASTTransformation.transform(
+ this.configureAST(tryCatchStatement, ctx)),
+ ctx);
+ }
+
+
+ @Override
+ public List<ExpressionStatement> visitResources(ResourcesContext ctx) {
+ return this.visitResourceList(ctx.resourceList());
+ }
+
+ @Override
+ public List<ExpressionStatement> visitResourceList(ResourceListContext ctx) {
+ return ctx.resource().stream().map(this::visitResource).collect(Collectors.toList());
+ }
+
+ @Override
+ public ExpressionStatement visitResource(ResourceContext ctx) {
+ if (asBoolean(ctx.localVariableDeclaration())) {
+ List<ExpressionStatement> declarationStatements = this.visitLocalVariableDeclaration(ctx.localVariableDeclaration()).getDeclarationStatements();
+
+ if (declarationStatements.size() > 1) {
+ throw createParsingFailedException("Multi resources can not be declared in one statement", ctx);
+ }
+
+ return declarationStatements.get(0);
+ } else if (asBoolean(ctx.expression())) {
+ Expression expression = (Expression) this.visit(ctx.expression());
+ if (!(expression instanceof BinaryExpression
+ && Types.ASSIGN == ((BinaryExpression) expression).getOperation().getType()
+ && ((BinaryExpression) expression).getLeftExpression() instanceof VariableExpression)) {
+
+ throw createParsingFailedException("Only variable declarations are allowed to declare resource", ctx);
+ }
+
+ BinaryExpression assignmentExpression = (BinaryExpression) expression;
+
+ return this.configureAST(
+ new ExpressionStatement(
+ this.configureAST(
+ new DeclarationExpression(
+ this.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
+ */
+ @Override
+ public List<CatchStatement> visitCatchClause(CatchClauseContext ctx) {
+ // FIXME Groovy will ignore variableModifier of parameter in the catch clause
+ // In order to make the new parser behave same with the old one, we do not process variableModifier*
+
+ return this.visitCatchType(ctx.catchType()).stream()
+ .map(e -> this.configureAST(
+ new CatchStatement(
+ // FIXME The old parser does not set location info for the parameter of the catch clause.
+ // we could make it better
+ //this.configureAST(new Parameter(e, this.visitIdentifier(ctx.identifier())), ctx.Identifier()),
+
+ new Parameter(e, this.visitIdentifier(ctx.identifier())),
+ this.visitBlock(ctx.block())),
+ ctx.block()))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ClassNode> visitCatchType(CatchTypeContext ctx) {
+ if (!asBoolean(ctx)) {
+ return Collections.singletonList(ClassHelper.OBJECT_TYPE);
+ }
+
+ return ctx.qualifiedClassName().stream()
+ .map(this::visitQualifiedClassName)
+ .collect(Collectors.toList());
+ }
+
+
+ @Override
+ public Statement visitFinallyBlock(FinallyBlockContext ctx) {
+ if (!asBoolean(ctx)) {
+ return EmptyStatement.INSTANCE;
+ }
+
+ return this.configureAST(
+ this.createBlockStatement((Statement) this.visit(ctx.block())),
+ ctx);
+ }
+
+ @Override
+ public SwitchStatement visitSwitchStmtAlt(SwitchStmtAltContext ctx) {
+ return this.configureAST(this.visitSwitchStatement(ctx.switchStatement()), ctx);
+ }
+
+ public SwitchStatement visitSwitchStatement(SwitchStatementContext ctx) {
+ List<Statement> statementList =
+ ctx.switchBlockStatementGroup().stream()
+ .map(this::visitSwitchBlockStatementGroup)
+ .reduce(new LinkedList<>(), (r, e) -> {
+ r.addAll(e);
+ return r;
+ });
+
+ List<CaseStatement> caseStatementList = new LinkedList<>();
+ List<Statement> defaultStatementList = new LinkedList<>();
+
+ statementList.forEach(e -> {
+ if (e instanceof CaseStatement) {
+ caseStatementList.add((CaseStatement) e);
+ } else if (isTrue(e, IS_SWITCH_DEFAULT)) {
+ defaultStatementList.add(e);
+ }
+ });
+
+ int defaultStatementListSize = defaultStatementList.size();
+ if (defaultStatementListSize > 1) {
+ throw createParsingFailedException("switch statement should have only one default case, which should appear at last", defaultStatementList.get(0));
+ }
+
+ if (defaultStatementListSize > 0 && last(statementList) instanceof CaseStatement) {
+ throw createParsingFailedException("default case should appear at last", defaultStatementList.get(0));
+ }
+
+ return this.configureAST(
+ new SwitchStatement(
+ this.visitParExpression(ctx.parExpression()),
+ caseStatementList,
+ defaultStatementListSize == 0 ? EmptyStatement.INSTANCE : defaultStatementList.get(0)
+ ),
+ ctx);
+
+ }
+
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public List<Statement> visitSwitchBlockStatementGroup(SwitchBlockStatementGroupContext ctx) {
+ int labelCnt = ctx.switchLabel().size();
+ List<Token> firstLabelHolder = new ArrayList<>(1);
+
+ return (List<Statement>) ctx.switchLabel().stream()
+ .map(e -> (Object) this.visitSwitchLabel(e))
+ .reduce(new ArrayList<Statement>(4), (r, e) -> {
+ List<Statement> statementList = (List<Statement>) r;
+ Pair<Token, Expression> pair = (Pair<Token, Expression>) e;
+
+ boolean isLast = labelCnt - 1 == statementList.size();
+
+ switch (pair.getKey().getType()) {
+ case CASE: {
+ if (!asBoolean(statementList)) {
+ firstLabelHolder.add(pair.getKey());
+ }
+
+ statementList.add(
+ this.configureAST(
+ new CaseStatement(
+ pair.getValue(),
+
+ // check whether processing the last label. if yes, block statement should be attached.
+ isLast ? this.visitBlockStatements(ctx.blockStatements())
+ : EmptyStatement.INSTANCE
+ ),
+ firstLabelHolder.get(0)));
+
+ break;
+ }
+ case DEFAULT: {
+
+ BlockStatement blockStatement = this.visitBlockStatements(ctx.blockStatements());
+ blockStatement.putNodeMetaData(IS_SWITCH_DEFAULT, true);
+
+ statementList.add(
+ // this.configureAST(blockStatement, pair.getKey())
+ blockStatement
+ );
+
+ break;
+ }
+ }
+
+ return statementList;
+ });
+
+ }
+
+ @Override
+ public Pair<Token, Expression> visitSwitchLabel(SwitchLabelContext ctx) {
+ if (asBoolean(ctx.CASE())) {
+ return new Pair<>(ctx.CASE().getSymbol(), (Expression) this.visit(ctx.expression()));
+ } else if (asBoolean(ctx.DEFAULT())) {
+ return new Pair<>(ctx.DEFAULT().getSymbol(), EmptyExpression.INSTANCE);
+ }
+
+ throw createParsingFailedException("Unsupported switch label: " + ctx.getText(), ctx);
+ }
+
+
+ @Override
+ public SynchronizedStatement visitSynchronizedStmtAlt(SynchronizedStmtAltContext ctx) {
+ return this.configureAST(
+ new SynchronizedStatement(this.visitParExpression(ctx.parExpression()), this.visitBlock(ctx.block())),
+ ctx);
+ }
+
+
+ @Override
+ public ExpressionStatement visitExpressionStmtAlt(ExpressionStmtAltContext ctx) {
+ return (ExpressionStatement) this.visit(ctx.statementExpression());
+ }
+
+ @Override
+ public ReturnStatement visitReturnStmtAlt(ReturnStmtAltContext ctx) {
+ return this.configureAST(new ReturnStatement(asBoolean(ctx.expression())
+ ? (Expression) this.visit(ctx.expression())
+ : ConstantExpression.EMPTY_EXPRESSION),
+ ctx);
+ }
+
+ @Override
+ public ThrowStatement visitThrowStmtAlt(ThrowStmtAltContext ctx) {
+ return this.configureAST(
+ new ThrowStatement((Expression) this.visit(ctx.expression())),
+ ctx);
+ }
+
+ @Override
+ public Statement visitLabeledStmtAlt(LabeledStmtAltContext ctx) {
+ Statement statement = (Statement) this.visit(ctx.statement());
+
+ statement.addStatementLabel(this.visitIdentifier(ctx.identifier()));
+
+ return statement; // this.configureAST(statement, ctx);
+ }
+
+ @Override
+ public BreakStatement visitBreakStatement(BreakStatementContext ctx) {
+ String label = asBoolean(ctx.identifier())
+ ? this.visitIdentifier(ctx.identifier())
+ : null;
+
+ return this.configureAST(new BreakStatement(label), ctx);
+ }
+
+ @Override
+ public BreakStatement visitBreakStmtAlt(BreakStmtAltContext ctx) {
+ return this.configureAST(this.visitBreakStatement(ctx.breakStatement()), ctx);
+ }
+
+ @Override
+ public ContinueStatement visitContinueStatement(ContinueStatementContext ctx) {
+ String label = asBoolean(ctx.identifier())
+ ? this.visitIdentifier(ctx.identifier())
+ : null;
+
+ return this.configureAST(new ContinueStatement(label), ctx);
+
+ }
+
+ @Override
+ public ContinueStatement visitContinueStmtAlt(ContinueStmtAltContext ctx) {
+ return this.configureAST(this.visitContinueStatement(ctx.continueStatement()), ctx);
+ }
+
+ @Override
+ public ImportNode visitImportStmtAlt(ImportStmtAltContext ctx) {
+ return this.configureAST(this.visitImportDeclaration(ctx.importDeclaration()), ctx);
+ }
+
+ @Override
+ public ClassNode visitTypeDeclarationStmtAlt(TypeDeclarationStmtAltContext ctx) {
+ return this.configureAST(this.visitTypeDeclaration(ctx.typeDeclaration()), ctx);
+ }
+
+
+ @Override
+ public Statement visitLocalVariableDeclarationStmtAlt(LocalVariableDeclarationStmtAltContext ctx) {
+ return this.configureAST(this.visitLocalVariableDeclaration(ctx.localVariableDeclaration()), ctx);
+ }
+
+ @Override
+ public MethodNode visitMethodDeclarationStmtAlt(MethodDeclarationStmtAltContext ctx) {
+ return this.configureAST(this.visitMethodDeclaration(ctx.methodDeclaration()), ctx);
+ }
+
+ // } statement --------------------------------------------------------------------
+
+ @Override
+ public ClassNode visitTypeDeclaration(TypeDeclarationContext ctx) {
+ if (asBoolean(ctx.classDeclaration())) { // e.g. class A {}
+ ctx.classDeclaration().putNodeMetaData(TYPE_DECLARATION_MODIFIERS, this.visitClassOrInterfaceModifiersOpt(ctx.classOrInterfaceModifiersOpt()));
+ return this.configureAST(this.visitClassDeclaration(ctx.classDeclaration()), ctx);
+ }
+
+ throw createParsingFailedException("Unsupported type declaration: " + ctx.getText(), ctx);
+ }
+
+ private void initUsingGenerics(ClassNode classNode) {
+ if (classNode.isUsingGenerics()) {
+ return;
+ }
+
+ if (!classNode.isEnum()) {
+ classNode.setUsingGenerics(classNode.getSuperClass().isUsingGenerics());
+ }
+
+ if (!classNode.isUsingGenerics() && asBoolean((Object) classNode.getInterfaces())) {
+ for (ClassNode anInterface : classNode.getInterfaces()) {
+ classNode.setUsingGenerics(classNode.isUsingGenerics() || anInterface.isUsingGenerics());
+
+ if (classNode.isUsingGenerics())
+ break;
+ }
+ }
+ }
+
+ @Override
+ public ClassNode visitClassDeclaration(ClassDeclarationContext ctx) {
+ String packageName = moduleNode.getPackageName();
+ packageName = asBoolean((Object) packageName) ? packageName : "";
+
+ List<ModifierNode> modifierNodeList = ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS);
+ Objects.requireNonNull(modifierNodeList, "modifierNodeList should not be null");
+
+ ModifierManager modifierManager = new ModifierManager(this, modifierNodeList);
+ int modifiers = modifierManager.getClassModifiersOpValue();
+
+ boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0);
+ modifiers &= ~Opcodes.ACC_SYNTHETIC;
+
+ final ClassNode outerClass = classNodeStack.peek();
+ ClassNode classNode;
+ String className = this.visitIdentifier(ctx.identifier());
+ if (asBoolean(ctx.ENUM())) {
+ classNode =
+ EnumHelper.makeEnumNode(
+ asBoolean(outerClass) ? className : packageName + className,
+ modifiers, null, outerClass);
+ } else {
+ if (asBoolean(outerClass)) {
+ classNode =
+ new InnerClassNode(
+ outerClass,
+ outerClass.getName() + "$" + className,
+ modifiers | (outerClass.isInterface() ? Opcodes.ACC_STATIC : 0),
+ ClassHelper.OBJECT_TYPE);
+ } else {
+ classNode =
+ new ClassNode(
+ packageName + className,
+ modifiers,
+ ClassHelper.OBJECT_TYPE);
+ }
+
+ }
+
+ this.configureAST(classNode, ctx);
+ classNode.putNodeMetaData(CLASS_NAME, className);
+ classNode.setSyntheticPublic(syntheticPublic);
+
+ if (asBoolean(ctx.TRAIT())) {
+ classNode.addAnnotation(new AnnotationNode(ClassHelper.make(GROOVY_TRANSFORM_TRAIT)));
+ }
+ classNode.addAnnotations(modifierManager.getAnnotations());
+ classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
+
+ boolean isInterface = asBoolean(ctx.INTERFACE()) && !asBoolean(ctx.AT());
+ boolean isInterfaceWithDefaultMethods = false;
+
+ // declaring interface with default method
+ if (isInterface && this.containsDefaultMethods(ctx)) {
+ isInterfaceWithDefaultMethods = true;
+ classNode.addAnnotation(new AnnotationNode(ClassHelper.make(GROOVY_TRANSFORM_TRAIT)));
+ classNode.putNodeMetaData(IS_INTERFACE_WITH_DEFAULT_METHODS, true);
+ }
+
+ if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT()) || isInterfaceWithDefaultMethods) { // class OR trait OR interface with default methods
+ classNode.setSuperClass(this.visitType(ctx.sc));
+ classNode.setInterfaces(this.visitTypeList(ctx.is));
+
+ this.initUsingGenerics(classNode);
+ } else if (isInterface) { // interface(NOT annotation)
+ classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT);
+
+ classNode.setSuperClass(ClassHelper.OBJECT_TYPE);
+ classNode.setInterfaces(this.visitTypeList(ctx.scs));
+
+ this.initUsingGenerics(classNode);
+
+ this.hackMixins(classNode);
+ } else if (asBoolean(ctx.ENUM())) { // enum
+ classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_ENUM | Opcodes.ACC_FINAL);
+
+ classNode.setInterfaces(this.visitTypeList(ctx.is));
+
+ this.initUsingGenerics(classNode);
+ } else if (asBoolean(ctx.AT())) { // annotation
+ classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_ANNOTATION);
+
+ classNode.addInterface(ClassHelper.Annotation_TYPE);
+
+ this.hackMixins(classNode);
+ } else {
+ throw createParsingFailedException("Unsupported class declaration: " + ctx.getText(), ctx);
+ }
+
+ // we put the class already in output to avoid the most inner classes
+ // will be used as first class later in the loader. The first class
+ // there determines what GCL#parseClass for example will return, so we
+ // have here to ensure it won't be the inner class
+ if (asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT())) {
+ classNodeList.add(classNode);
+ }
+
+ int oldAnonymousInnerClassCounter = this.anonymousInnerClassCounter;
+ classNodeStack.push(classNode);
+ ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitClassBody(ctx.classBody());
+ classNodeStack.pop();
+ this.anonymousInnerClassCounter = oldAnonymousInnerClassCounter;
+
+ if (!(asBoolean(ctx.CLASS()) || asBoolean(ctx.TRAIT()))) {
+ classNodeList.add(classNode);
+ }
+
+ groovydocManager.handle(classNode, ctx);
+
+ return classNode;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private boolean containsDefaultMethods(ClassDeclarationContext ctx) {
+ List<MethodDeclarationContext> methodDeclarationContextList =
+ (List<MethodDeclarationContext>) ctx.classBody().classBodyDeclaration().stream()
+ .map(ClassBodyDeclarationContext::memberDeclaration)
+ .filter(Objects::nonNull)
+ .map(e -> (Object) e.methodDeclaration())
+ .filter(Objects::nonNull).reduce(new LinkedList<MethodDeclarationContext>(), (r, e) -> {
+ MethodDeclarationContext methodDeclarationContext = (MethodDeclarationContext) e;
+
+ if (createModifierManager(methodDeclarationContext).contains(DEFAULT)) {
+ ((List) r).add(methodDeclarationContext);
+ }
+
+ return r;
+ });
+
+ return !methodDeclarationContextList.isEmpty();
+ }
+
+ @Override
+ public Void visitClassBody(ClassBodyContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ if (asBoolean(ctx.enumConstants())) {
+ ctx.enumConstants().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitEnumConstants(ctx.enumConstants());
+ }
+
+ ctx.classBodyDeclaration().forEach(e -> {
+ e.putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitClassBodyDeclaration(e);
+ });
+
+ return null;
+ }
+
+ @Override
+ public List<FieldNode> visitEnumConstants(EnumConstantsContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ return ctx.enumConstant().stream()
+ .map(e -> {
+ e.putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ return this.visitEnumConstant(e);
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public FieldNode visitEnumConstant(EnumConstantContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ InnerClassNode anonymousInnerClassNode = null;
+ if (asBoolean(ctx.anonymousInnerClassDeclaration())) {
+ ctx.anonymousInnerClassDeclaration().putNodeMetaData(ANONYMOUS_INNER_CLASS_SUPER_CLASS, classNode);
+ anonymousInnerClassNode = this.visitAnonymousInnerClassDeclaration(ctx.anonymousInnerClassDeclaration());
+ }
+
+ FieldNode enumConstant =
+ EnumHelper.addEnumConstant(
+ classNode,
+ this.visitIdentifier(ctx.identifier()),
+ createEnumConstantInitExpression(ctx.arguments(), anonymousInnerClassNode));
+
+ this.visitAnnotationsOpt(ctx.annotationsOpt()).forEach(enumConstant::addAnnotation);
+
+ groovydocManager.handle(enumConstant, ctx);
+
+ return this.configureAST(enumConstant, ctx);
+ }
+
+ private Expression createEnumConstantInitExpression(ArgumentsContext ctx, InnerClassNode anonymousInnerClassNode) {
+ if (!asBoolean(ctx) && !asBoolean(anonymousInnerClassNode)) {
+ return null;
+ }
+
+ TupleExpression argumentListExpression = (TupleExpression) this.visitArguments(ctx);
+ List<Expression> expressions = argumentListExpression.getExpressions();
+
+ if (expressions.size() == 1) {
+ Expression expression = expressions.get(0);
+
+ if (expression instanceof NamedArgumentListExpression) { // e.g. SOME_ENUM_CONSTANT(a: "1", b: "2")
+ List<MapEntryExpression> mapEntryExpressionList = ((NamedArgumentListExpression) expression).getMapEntryExpressions();
+ ListExpression listExpression =
+ new ListExpression(
+ mapEntryExpressionList.stream()
+ .map(e -> (Expression) e)
+ .collect(Collectors.toList()));
+
+ if (asBoolean(anonymousInnerClassNode)) {
+ listExpression.addExpression(
+ this.configureAST(
+ new ClassExpression(anonymousInnerClassNode),
+ anonymousInnerClassNode));
+ }
+
+ if (mapEntryExpressionList.size() > 1) {
+ listExpression.setWrapped(true);
+ }
+
+ return this.configureAST(listExpression, ctx);
+ }
+
+ if (!asBoolean(anonymousInnerClassNode)) {
+ if (expression instanceof ListExpression) {
+ ListExpression listExpression = new ListExpression();
+ listExpression.addExpression(expression);
+
+ return this.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(
+ this.configureAST(
+ new ClassExpression(anonymousInnerClassNode),
+ anonymousInnerClassNode));
+
+ return this.configureAST(listExpression, ctx);
+ }
+
+ ListExpression listExpression = new ListExpression(expressions);
+ if (asBoolean(anonymousInnerClassNode)) {
+ listExpression.addExpression(
+ this.configureAST(
+ new ClassExpression(anonymousInnerClassNode),
+ anonymousInnerClassNode));
+ }
+
+ if (asBoolean(ctx)) {
+ listExpression.setWrapped(true);
+ }
+
+ return asBoolean(ctx)
+ ? this.configureAST(listExpression, ctx)
+ : this.configureAST(listExpression, anonymousInnerClassNode);
+ }
+
+
+ @Override
+ public Void visitClassBodyDeclaration(ClassBodyDeclarationContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ if (asBoolean(ctx.memberDeclaration())) {
+ ctx.memberDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitMemberDeclaration(ctx.memberDeclaration());
+ } else if (asBoolean(ctx.block())) {
+ Statement statement = this.visitBlock(ctx.block());
+
+ if (asBoolean(ctx.STATIC())) { // e.g. static { }
+ classNode.addStaticInitializerStatements(Collections.singletonList(statement), false);
+ } else { // e.g. { }
+ classNode.addObjectInitializerStatements(
+ this.configureAST(
+ this.createBlockStatement(statement),
+ statement));
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Void visitMemberDeclaration(MemberDeclarationContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ if (asBoolean(ctx.methodDeclaration())) {
+ ctx.methodDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitMethodDeclaration(ctx.methodDeclaration());
+ } else if (asBoolean(ctx.fieldDeclaration())) {
+ ctx.fieldDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitFieldDeclaration(ctx.fieldDeclaration());
+ } else if (asBoolean(ctx.classDeclaration())) {
+ ctx.classDeclaration().putNodeMetaData(TYPE_DECLARATION_MODIFIERS, this.visitModifiersOpt(ctx.modifiersOpt()));
+ ctx.classDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitClassDeclaration(ctx.classDeclaration());
+ }
+
+ return null;
+ }
+
+ @Override
+ public GenericsType[] visitTypeParameters(TypeParametersContext ctx) {
+ if (!asBoolean(ctx)) {
+ return null;
+ }
+
+ return ctx.typeParameter().stream()
+ .map(this::visitTypeParameter)
+ .toArray(GenericsType[]::new);
+ }
+
+ @Override
+ public GenericsType visitTypeParameter(TypeParameterContext ctx) {
+ return this.configureAST(
+ new GenericsType(
+ ClassHelper.make(this.visitClassName(ctx.className())),
+ this.visitTypeBound(ctx.typeBound()),
+ null
+ ),
+ ctx);
+ }
+
+ @Override
+ public ClassNode[] visitTypeBound(TypeBoundContext ctx) {
+ if (!asBoolean(ctx)) {
+ return null;
+ }
+
+ return ctx.type().stream()
+ .map(this::visitType)
+ .toArray(ClassNode[]::new);
+ }
+
+ @Override
+ public Void visitFieldDeclaration(FieldDeclarationContext ctx) {
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ Objects.requireNonNull(classNode, "classNode should not be null");
+
+ ctx.variableDeclaration().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode);
+ this.visitVariableDeclaration(ctx.variableDeclaration());
+
+ return null;
+ }
+
+ private ConstructorCallExpression checkThisAndSuperConstructorCall(Statement statement) {
+ if (!(statement instanceof BlockStatement)) { // method code must be a BlockStatement
+ return null;
+ }
+
+ BlockStatement blockStatement = (BlockStatement) statement;
+ List<Statement> statementList = blockStatement.getStatements();
+
+ for (int i = 0, n = statementList.size(); i < n; i++) {
+ Statement s = statementList.get(i);
+ if (s instanceof ExpressionStatement) {
+ Expression expression = ((ExpressionStatement) s).getExpression();
+ if ((expression instanceof ConstructorCallExpression) && 0 != i) {
+ return (ConstructorCallExpression) expression;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private ModifierManager createModifierManager(MethodDeclarationContext ctx) {
+ List<ModifierNode> modifierNodeList = Collections.emptyList();
+
+ if (asBoolean(ctx.modifiers())) {
+ modifierNodeList = this.visitModifiers(ctx.modifiers());
+ } else if (asBoolean(ctx.modifiersOpt())) {
+ modifierNodeList = this.visitModifiersOpt(ctx.modifiersOpt());
+ }
+
+ return new ModifierManager(this, modifierNodeList);
+ }
+
+ private void validateParametersOfMethodDeclaration(Parameter[] parameters, ClassNode classNode) {
+ if (!classNode.isInterface()) {
+ return;
+ }
+
+ Arrays.stream(parameters).forEach(e -> {
+ if (e.hasInitialExpression()) {
+ throw createParsingFailedException("Cannot specify default value for method parameter '" + e.getName() + " = " + e.getInitialExpression().getText() + "' inside an interface", e);
+ }
+ });
+ }
+
+ @Override
+ public MethodNode visitMethodDeclaration(MethodDeclarationContext ctx) {
+ ModifierManager modifierManager = createModifierManager(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<InnerClassNode> anonymousInnerClassList = anonymousInnerClassesDefinedInMethodStack.pop();
+
+ MethodNode methodNode;
+ // if classNode is not null, the method declaration is for class declaration
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+ if (asBoolean(classNode)) {
+ 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.contains(STATIC)) {
+ Arrays.stream(methodNode.getParameters()).forEach(e -> e.setInStaticContext(true));
+ methodNode.getVariableScope().setInStaticContext(true);
+ }
+
+ this.configureAST(methodNode, ctx);
+
+ validateMethodDeclaration(ctx, methodNode, modifierManager, classNode);
+
+ groovydocManager.handle(methodNode, ctx);
+
+ return methodNode;
+ }
+
+ private void validateMethodDeclaration(MethodDeclarationContext ctx, MethodNode methodNode, ModifierManager modifierManager, ClassNode classNode) {
+ boolean isAbstractMethod = methodNode.isAbstract();
+ boolean hasMethodBody = asBoolean(methodNode.getCode());
+
+ if (9 == ctx.ct) { // script
+ if (isAbstractMethod || !hasMethodBody) { // method should not be declared abstract in the script
+ throw createParsingFailedException("You can not define a " + (isAbstractMethod ? "abstract" : "") + " method[" + methodNode.getName() + "] " + (!hasMethodBody ? "without method body" : "") + " in the script. Try " + (isAbstractMethod ? "removing the 'abstract'" : "") + (isAbstractMethod && !hasMethodBody ? " and" : "") + (!hasMethodBody ? " adding a method body" : ""), methodNode);
+ }
+ } else {
+ if (!isAbstractMethod && !hasMethodBody) { // non-abstract method without body in the non-script(e.g. class, enum, trait) is not allowed!
+ throw createParsingFailedException("You defined a method[" + methodNode.getName() + "] without body. Try adding a method body, or declare it abstract", methodNode);
+ }
+
+ boolean isInterfaceOrAbstractClass = asBoolean(classNode) && classNode.isAbstract() && !classNode.isAnnotationDefinition();
+ if (isInterfaceOrAbstractClass && !modifierManager.contains(DEFAULT) && isAbstractMethod && hasMethodBody) {
+ throw createParsingFailedException("You defined an abstract method[" + methodNode.getName() + "] with body. Try removing the method body" + (classNode.isInterface() ? ", or declare it default" : ""), methodNode);
+ }
+ }
+
+ modifierManager.validate(methodNode);
+
+ if (methodNode instanceof ConstructorNode) {
+ modifierManager.validate((ConstructorNode) methodNode);
+ }
+ }
+
+ private MethodNode createScriptMethodNode(ModifierManager modifierManager, String methodName, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code) {
+ MethodNode methodNode;
+ methodNode =
+ new MethodNode(
+ methodName,
+ modifierManager.contains(PRIVATE) ? Opcodes.ACC_PRIVATE : Opcodes.ACC_PUBLIC,
+ returnType,
+ parameters,
+ exceptions,
+ code);
+
+ modifierManager.processMethodNode(methodNode);
+ return methodNode;
+ }
+
+ private MethodNode createConstructorOrMethodNodeForClass(MethodDeclarationContext ctx, ModifierManager modifierManager, String methodName, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code, ClassNode classNode) {
+ MethodNode methodNode;
+ String className = classNode.getNodeMetaData(CLASS_NAME);
+ int modifiers = modifierManager.getClassMemberModifiersOpValue();
+
+ if (!asBoolean(ctx.returnType())
+ && asBoolean(ctx.methodBody())
+ && methodName.equals(className)) { // constructor declaration
+
+ methodNode = createConstructorNodeForClass(methodName, parameters, exceptions, code, classNode, modifiers);
+ } else { // class memeber method declaration
+ methodNode = createMethodNodeForClass(ctx, modifierManager, methodName, returnType, parameters, exceptions, code, classNode, modifiers);
+ }
+
+ modifierManager.attachAnnotations(methodNode);
+ return methodNode;
+ }
+
+ private MethodNode createMethodNodeForClass(MethodDeclarationContext ctx, ModifierManager modifierManager, String methodName, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code, ClassNode classNode, int modifiers) {
+ MethodNode methodNode;
+ if (asBoolean(ctx.elementValue())) { // the code of annotation method
+ code = this.configureAST(
+ new ExpressionStatement(
+ this.visitElementValue(ctx.elementValue())),
+ ctx.elementValue());
+
+ }
+
+ modifiers |= !modifierManager.contains(STATIC) && (classNode.isInterface() || (isTrue(classNode, IS_INTERFACE_WITH_DEFAULT_METHODS) && !modifierManager.contains(DEFAULT))) ? Opcodes.ACC_ABSTRACT : 0;
+
+ checkWhetherMethodNodeWithSameSignatureExists(classNode, methodName, parameters, ctx);
+
+ methodNode = classNode.addMethod(methodName, modifiers, returnType, parameters, exceptions, code);
+
+ methodNode.setAnnotationDefault(asBoolean(ctx.elementValue()));
+ return methodNode;
+ }
+
+ private void checkWhetherMethodNodeWithSameSignatureExists(ClassNode classNode, String methodName, Parameter[] parameters, MethodDeclarationContext ctx) {
+ MethodNode sameSigMethodNode = classNode.getDeclaredMethod(methodName, parameters);
+
+ if (null == sameSigMethodNode) {
+ return;
+ }
+
+ throw createParsingFailedException("The method " + sameSigMethodNode.getText() + " duplicates another method of the same signature", ctx);
+ }
+
+ private ConstructorNode createConstructorNodeForClass(String methodName, Parameter[] parameters, ClassNode[] exceptions, Statement code, ClassNode classNode, 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(MethodNameContext ctx) {
+ if (asBoolean(ctx.identifier())) {
+ return this.visitIdentifier(ctx.identifier());
+ }
+
+ if (asBoolean(ctx.stringLiteral())) {
+ return this.visitStringLiteral(ctx.stringLiteral()).getText();
+ }
+
+ throw createParsingFailedException("Unsupported method name: " + ctx.getText(), ctx);
+ }
+
+ @Override
+ public ClassNode visitReturnType(ReturnTypeContext ctx) {
+ if (!asBoolean(ctx)) {
+ return ClassHelper.OBJECT_TYPE;
+ }
+
+ if (asBoolean(ctx.type())) {
+ return this.visitType(ctx.type());
+ }
+
+ if (asBoolean(ctx.VOID())) {
+ return ClassHelper.VOID_TYPE;
+ }
+
+ throw createParsingFailedException("Unsupported return type: " + ctx.getText(), ctx);
+ }
+
+ @Override
+ public Statement visitMethodBody(MethodBodyContext ctx) {
+ if (!asBoolean(ctx)) {
+ return null;
+ }
+
+ return this.configureAST(this.visitBlock(ctx.block()), ctx);
+ }
+
+ @Override
+ public DeclarationListStatement visitLocalVariableDeclaration(LocalVariableDeclarationContext ctx) {
+ return this.configureAST(this.visitVariableDeclaration(ctx.variableDeclaration()), ctx);
+ }
+
+ private ModifierManager createModifierManager(VariableDeclarationContext ctx) {
+ List<ModifierNode> modifierNodeList = Collections.emptyList();
+
+ if (asBoolean(ctx.variableModifiers())) {
+ modifierNodeList = this.visitVariableModifiers(ctx.variableModifiers());
+ } else if (asBoolean(ctx.variableModifiersOpt())) {
+ modifierNodeList = this.visitVariableModifiersOpt(ctx.variableModifiersOpt());
+ } else if (asBoolean(ctx.modifiers())) {
+ modifierNodeList = this.visitModifiers(ctx.modifiers());
+ } else if (asBoolean(ctx.modifiersOpt())) {
+ modifierNodeList = this.visitModifiersOpt(ctx.modifiersOpt());
+ }
+
+ return new ModifierManager(this, modifierNodeList);
+ }
+
+ private DeclarationListStatement createMultiAssignmentDeclarationListStatement(VariableDeclarationContext ctx, ModifierManager modifierManager) {
+ if (!modifierManager.contains(DEF)) {
+ throw createParsingFailedException("keyword def is required to declare tuple, e.g. def (int a, int b) = [1, 2]", ctx);
+ }
+
+ return this.configureAST(
+ new DeclarationListStatement(
+ this.configureAST(
+ modifierManager.attachAnnotations(
+ new DeclarationExpression(
+ new ArgumentListExpression(
+ this.visitTypeNamePairs(ctx.typeNamePairs()).stream()
+ .peek(e -> modifierManager.processVariableExpression((VariableExpression) e))
+ .collect(Collectors.toList())
+ ),
+ this.createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN),
+ this.visitVariableInitializer(ctx.variableInitializer())
+ )
+ ),
+ ctx
+ )
+ ),
+ ctx
+ );
+ }
+
+ @Override
+ public DeclarationListStatement visitVariableDeclaration(VariableDeclarationContext ctx) {
+ ModifierManager modifierManager = this.createModifierManager(ctx);
+
+ 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<DeclarationExpression> declarationExpressionList = this.visitVariableDeclarators(ctx.variableDeclarators());
+
+ // if classNode is not null, the variable declaration is for class declaration. In other words, it is a field declaration
+ ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
+
+ if (asBoolean(classNode)) {
+ return createFieldDeclarationListStatement(ctx, modifierManager, variableType, declarationExpressionList, classNode);
+ }
+
+ declarationExpressionList.forEach(e -> {
+ VariableExpression variableExpression = (VariableExpression) e.getLeftExpression();
+
+ modifierManager.processVariableExpression(variableExpression);
+ modifierManager.attachAnnotations(e);
+ });
+
+ int size = declarationExpressionList.size();
+ if (size > 0) {
+ DeclarationExpression declarationExpression = declarationExpressionList.get(0);
+
+ if (1 == size) {
+ this.configureAST(declarationExpression, ctx);
+ } else {
+ // Tweak start of first declaration
+ declarationExpression.setLineNumber(ctx.getStart().getLine());
+ declarationExpression.setColumnNumber(ctx.getStart().getCharPositionInLine() + 1);
+ }
+ }
+
+ return this.configureAST(new DeclarationListStatement(declarationExpressionList), ctx);
+ }
+
+ private DeclarationListStatement createFieldDeclarationListStatement(VariableDeclarationContext ctx, ModifierManager modifierManager, ClassNode variableType, List<DeclarationExpression> declarationExpressionList, ClassNode classNode) {
+ declarationExpressionList.forEach(e -> {
+ VariableExpression variableExpression = (VariableExpression) e.getLeftExpression();
+
+ int modifiers = modifierManager.getClassMemberModifiersOpValue();
+
+ Expression initialValue = EmptyExpression.INSTANCE.equals(e.getRightExpression()) ? null : e.getRightExpression();
+ Object defaultValue = findDefaultValueByType(variableType);
+
+ if (classNode.isInterface()) {
+ if (!asBoolean(initialValue)) {
+ initialValue = !asBoolean(defaultValue) ? null : new ConstantExpression(defaultValue);
+ }
+
+ modifiers |= Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
+ }
+
+ if (classNode.isInterface() || modifierManager.containsVisibilityModifier()) {
+ FieldNode fieldNode =
+ classNode.addField(
+ variableExpression.getName(),
+ modifiers,
+ variableType,
+ initialValue);
+ modifierManager.attachAnnotations(fieldNode);
+
+ groovydocManager.handle(fieldNode, ctx);
+
+ this.configureAST(fieldNode, ctx);
+ } else {
+ PropertyNode propertyNode =
+ classNode.addProperty(
+ variableExpression.getName(),
+ modifiers | Opcodes.ACC_PUBLIC,
+ variableType,
+ initialValue,
+ null,
+ null);
+
+ FieldNode fieldNode = propertyNode.getField();
+ fieldNode.setModifiers(modifiers & ~Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE);
+ fieldNode.setSynthetic(!classNode.isInterface());
+ modifierManager.attachAnnotations(fieldNode);
+
+ groovydocManager.handle(fieldNode, ctx);
+ groovydocManager.handle(propertyNode, ctx);
+
+ this.configureAST(fieldNode, ctx);
+ this.configureAST(propertyNode, ctx);
+ }
+
+ });
+
+ return null;
+ }
+
+ @Override
+ public List<Expression> visitTypeNamePairs(TypeNamePairsContext ctx) {
+ return ctx.typeNamePair().stream().map(this::visitTypeNamePair).collect(Collectors.toList());
+ }
+
+ @Override
+ public VariableExpression visitTypeNamePair(TypeNamePairContext ctx) {
+ return this.configureAST(
+ new VariableExpression(
+ this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
+ this.visitType(ctx.type())),
+ ctx);
+ }
+
+ @Override
+ public List<DeclarationExpression> visitVariableDeclarators(VariableDeclaratorsContext ctx) {
+ ClassNode variableType = ctx.getNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE);
+ Objects.requireNonNull(variableType, "variableType should not be null");
+
+ return ctx.variableDeclarator().stream()
+ .map(e -> {
+ e.putNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE, variableType);
+ return this.visitVariableDeclarator(e);
+// return this.configureAST(this.visitVariableDeclarator(e), ctx);
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public DeclarationExpression visitVariableDeclarator(VariableDeclaratorContext ctx) {
+ ClassNode variableType = ctx.getNodeMetaData(VARIABLE_DECLARATION_VARIABLE_TYPE);
+ Objects.requireNonNull(variableType, "variableType should not be null");
+
+ org.codehaus.groovy.syntax.Token token;
+ if (asBoolean(ctx.ASSIGN())) {
+ token = createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN);
+ } else {
+ token = new org.codehaus.groovy.syntax.Token(Types.ASSIGN, ASSIGN_STR, ctx.start.getLine(), 1);
+ }
+
+ return this.configureAST(
+ new DeclarationExpression(
+ this.configureAST(
+ new VariableExpression(
+ this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
+ variableType
+ ),
+ ctx.variableDeclaratorId()),
+ token,
+ this.visitVariableInitializer(ctx.variableInitializer())),
+ ctx);
+ }
+
+ @Override
+ public Expression visitVariableInitializer(VariableInitializerContext ctx) {
+ if (!asBoolean(ctx)) {
+ return EmptyExpression.INSTANCE;
+ }
+
+ if (asBoolean(ctx.statementExpression())) {
+ return this.configureAST(
+ ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression(),
+ ctx);
+ }
+
+ if (asBoolean(ctx.standardLambda())) {
+ return this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
+ }
+
+ throw createParsingFailedException("Unsupported variable initializer: " + ctx.getText(), ctx);
+ }
+
+ @Override
+ public List<Expression> visitVariableInitializers(VariableInitializersContext ctx) {
+ if (!asBoolean(ctx)) {
+ return Collections.emptyList();
+ }
+
+ return ctx.variableInitializer().stream()
+ .map(this::visitVariableInitializer)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Expression> visitArrayInitializer(ArrayInitializerContext ctx) {
+ if (!asBoolean(ctx)) {
+ return Collections.emptyList();
+ }
+
+ return this.visitVariableInitializers(ctx.variableInitializers());
+ }
+
+ @Override
+ public Statement visitBlock(BlockContext ctx) {
+ if (!asBoolean(ctx)) {
+ return this.createBlockStatement();
+ }
+
+ return this.configureAST(
+ this.visitBlockStatementsOpt(ctx.blockStatementsOpt()),
+ ctx);
+ }
+
+
+ @Override
+ public ExpressionStatement visitNormalExprAlt(NormalExprAltContext ctx) {
+ return this.configureAST(new ExpressionStatement((Expression) this.visit(ctx.expression())), ctx);
+ }
+
+ @Override
+ public ExpressionStatement visitCommandExprAlt(CommandExprAltContext ctx) {
+ return this.configureAST(new ExpressionStatement(this.visitCommandExpression(ctx.commandExpression())), ctx);
+ }
+
+ @Override
+ public Expression visitCommandExpression(CommandExpressionContext ctx) {
+ Expression baseExpr = this.visitPathExpression(ctx.pathExpression());
+ Expression arguments = this.visitEnhancedArgumentList(ctx.enhancedArgumentList());
+
+ MethodCallExpression methodCallExpression;
+ if (baseExpr instanceof PropertyExpression) { // e.g. obj.a 1, 2
+ methodCallExpression =
+ this.configureAST(
+ this.createMethodCallExpression(
+ (PropertyExpression) baseExpr, arguments),
+ arguments);
+
+ } else if (baseExpr instanceof MethodCallExpression && !isTrue(baseExpr, IS_INSIDE_PARENTHESES)) { // e.g. m {} a, b OR m(...) a, b
+ if (asBoolean(arguments)) {
+ // The error should never be thrown.
+ throw new GroovyBugError("When baseExpr is a instance of MethodCallExpression, which should follow NO argumentList");
+ }
+
+ methodCallExpression = (MethodCallExpression) baseExpr;
+ } else if (
+ !isTrue(baseExpr, IS_INSIDE_PARENTHESES)
+ && (baseExpr instanceof VariableExpression /* e.g. m 1, 2 */
+ || baseExpr instanceof GStringExpression /* e.g. "$m" 1, 2 */
+ || (baseExpr instanceof ConstantExpression && isTrue(baseExpr, IS_STRING)) /* e.g. "m" 1, 2 */)
+ ) {
+ methodCallExpression =
+ this.configureAST(
+ this.createMethodCallExpression(baseExpr, arguments),
+ arguments);
+ } else { // e.g. a[x] b, new A() b, etc.
+ methodCallExpression =
+ this.configureAST(
+ new MethodCallExpression(
+ baseExpr,
+ CALL_STR,
+ arguments
+ ),
+ arguments
+ );
+
+ methodCallExpression.setImplicitThis(false);
+ }
+
+ if (!asBoolean(ctx.commandArgument())) {
+ return this.configureAST(methodCallExpression, ctx);
+ }
+
+ return this.configureAST(
+ (Expression) ctx.commandArgument().stream()
+ .map(e -> (Object) e)
+ .reduce(methodCallExpression,
+ (r, e) -> {
+ CommandArgumentContext commandArgumentContext = (CommandArgumentContext) e;
+ commandArgumentContext.putNodeMetaData(CMD_EXPRESSION_BASE_EXPR, r);
+
+ return this.visitCommandArgument(commandArgumentContext);
+ }
+ ),
+ ctx);
+ }
+
+ @Override
+ public Expression visitCommandArgument(CommandArgumentContext ctx) {
+ // e.g. x y a b we call "x y" as the base expression
+ Expression baseExpr = ctx.getNodeMetaData(CMD_EXPRESSION_BASE_EXPR);
+
+ Expression primaryExpr = (Expression) this.visit(ctx.primary());
+
+ if (asBoolean(ctx.enhancedArgumentList())) { // e.g. x y a b
+ if (baseExpr instanceof PropertyExpression) { // the branch should never reach, because a.b.c will be parsed as a path expression, not a method call
+ throw createParsingFailedException("Unsupported command argument: " + ctx.getText(), ctx);
+ }
+
+ // the following code will process "a b" of "x y a b"
+ MethodCallExpression methodCallExpression =
+ new MethodCallExpression(
+ baseExpr,
+ this.createConstantExpression(primaryExpr),
+ this.visitEnhancedArgumentList(ctx.enhancedArgumentList())
+ );
+ methodCallExpression.setImplicitThis(false);
+
+ return this.configureAST(methodCallExpression, ctx);
+ } else if (asBoolean(ctx.pathElement())) { // e.g. x y a.b
+ Expression pathExpression =
+ this.createPathExpression(
+ this.configureAST(
+ new PropertyExpression(baseExpr, this.createConstantExpression(primaryExpr)),
+ primaryExpr
+ ),
+ ctx.pathElement()
+ );
+
+ return this.configureAST(pathExpression, ctx);
+ }
+
+ // e.g. x y a
+ return this.configureAST(
+ new PropertyExpression(
+ baseExpr,
+ primaryExpr instanceof VariableExpression
+ ? this.createConstantExpression(primaryExpr)
+ : primaryExpr
+ ),
+ primaryExpr
+ );
+ }
+
+
+ // expression { --------------------------------------------------------------------
+
+ @Override
+ public ClassNode visitCastParExpression(CastParExpressionContext ctx) {
+ return this.visitType(ctx.type());
+ }
+
+ @Override
+ public Expression visitParExpression(ParExpressionContext ctx) {
+ Expression expression;
+
+ if (asBoolean(ctx.statementExpression())) {
+ expression = ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression();
+ } else if (asBoolean(ctx.standardLambda())) {
+ expression = this.visitStandardLambda(ctx.standardLambda());
+ } else {
+ throw createParsingFailedException("Unsupported parentheses expression: " + ctx.getText(), ctx);
+ }
+
+ expression.putNodeMetaData(IS_INSIDE_PARENTHESES, true);
+
+ Integer insideParenLevel = expression.getNodeMetaData(INSIDE_PARENTHESES_LEVEL);
+ if (asBoolean((Object) insideParenLevel)) {
+ insideParenLevel++;
+ } else {
+ insideParenLevel = 1;
+ }
+ expression.putNodeMetaData(INSIDE_PARENTHESES_LEVEL, insideParenLevel);
+
+ return this.configureAST(expression, ctx);
+ }
+
+ @Override
+ public Expression visitPathExpression(PathExpressionContext ctx) {
+ return this.configureAST(
+ this.createPathExpression((Expression) this.visit(ctx.primary()), ctx.pathElement()),
+ ctx);
+ }
+
+ @Override
+ public Expression visitPathElement(PathElementContext ctx) {
+ Expression baseExpr = ctx.getNodeMetaData(PATH_EXPRESSION_BASE_EXPR);
+ Objects.requireNonNull(baseExpr, "baseExpr is required!");
+
+ if (asBoolean(ctx.namePart())) {
+ Expression namePartExpr = this.visitNamePart(ctx.namePart());
+ GenericsType[] genericsTypes = this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments());
+
+
+ if (asBoolean(ctx.DOT())) {
+ if (asBoolean(ctx.AT())) { // e.g. obj.@a
+ return this.configureAST(new AttributeExpression(baseExpr, namePartExpr), ctx);
+ } else { // e.g. obj.p
+ PropertyExpression propertyExpression = new PropertyExpression(baseExpr, namePartExpr);
+ propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, genericsTypes);
+
+ return this.configureAST(propertyExpression, ctx);
+ }
+ } else if (asBoolean(ctx.SAFE_DOT())) {
+ if (asBoolean(ctx.AT())) { // e.g. obj?.@a
+ return this.configureAST(new AttributeExpression(baseExpr, namePartExpr, true), ctx);
+ } else { // e.g. obj?.p
+ PropertyExpression propertyExpression = new PropertyExpression(baseExpr, namePartExpr, true);
+ propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, genericsTypes);
+
+ return this.configureAST(propertyExpression, ctx);
+ }
+ } else if (asBoolean(ctx.METHOD_POINTER())) { // e.g. obj.&m
+ return this.configureAST(new MethodPointerExpression(baseExpr, namePartExpr), ctx);
+ } else if (asBoolean(ctx.METHOD_REFERENCE())) { // e.g. obj::m
+ return this.configureAST(new MethodReferenceExpression(baseExpr, namePartExpr), ctx);
+ } else if (asBoolean(ctx.SPREAD_DOT())) {
+ if (asBoolean(ctx.AT())) { // e.g. obj*.@a
+ AttributeExpression attributeExpression = new AttributeExpression(baseExpr, namePartExpr, true);
+
+ attributeExpression.setSpreadSafe(true);
+
+ return this.configureAST(attributeExpression, ctx);
+ } else { // e.g. obj*.p
+ PropertyExpression propertyExpression = new PropertyExpression(baseExpr, namePartExpr, true);
+ propertyExpression.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, genericsTypes);
+
+ propertyExpression.setSpreadSafe(true);
+
+ return this.configureAST(propertyExpression, ctx);
+ }
+ }
+ }
+
+ if (asBoolean(ctx.indexPropertyArgs())) { // e.g. list[1, 3, 5]
+ Pair<Token, Expression> pair = this.visitIndexPropertyArgs(ctx.indexPropertyArgs());
+
+ return this.configureAST(
+ new BinaryExpression(baseExpr, createGroovyToken(pair.getKey()), pair.getValue(), asBoolean(ctx.indexPropertyArgs().QUESTION())),
+ ctx);
+ }
+
+ if (asBoolean(ctx.namedPropertyArgs())) { // this is a special way to new instance, e.g. Person(name: 'Daniel.Sun', location: 'Shanghai')
+ List<MapEntryExpression> mapEntryExpressionList =
+ this.visitNamedPropertyArgs(ctx.namedPropertyArgs());
+
+ Expression right;
+ if (mapEntryExpressionList.size() == 1) {
+ MapEntryExpression mapEntryExpression = mapEntryExpressionList.get(0);
+
+ if (mapEntryExpression.getKeyExpression() instanceof SpreadMapExpression) {
+ right = mapEntryExpression.getKeyExpression();
+ } else {
+ right = mapEntryExpression;
+ }
+ } else {
+ ListExpression listExpression =
+ this.configureAST(
+ new ListExpression(
+ mapEntryExpressionList.stream()
+ .map(
+ e -> {
+ if (e.getKeyExpression() instanceof SpreadMapExpression) {
+ return e.getKeyExpression();
+ }
+
+ return e;
+ }
+ )
+ .collect(Collectors.toList())),
+ ctx.namedPropertyArgs()
+ );
+ listExpression.setWrapped(true);
+ right = listExpression;
+ }
+
+ return this.configureAST(
+ new BinaryExpression(baseExpr, createGroovyToken(ctx.namedPropertyArgs().LBRACK().getSymbol()), right),
+ ctx);
+ }
+
+ if (asBoolean(ctx.arguments())) {
+ Expression argumentsExpr = this.visitArguments(ctx.arguments());
+
+ if (isTrue(baseExpr, IS_INSIDE_PARENTHESES)) { // e.g. (obj.x)(), (obj.@x)()
+ MethodCallExpression methodCallExpression =
+ new MethodCallExpression(
+ baseExpr,
+ CALL_STR,
+ argumentsExpr
+ );
+
+ methodCallExpression.setImplicitThis(false);
+
+ return this.configureAST(methodCallExpression, ctx);
+ }
+
+ if (baseExpr instanceof AttributeExpression) { // e.g. obj.@a(1, 2)
+ AttributeExpression attributeExpression = (AttributeExpression) baseExpr;
+ attributeExpression.setSpreadSafe(false); // whether attributeExpression is spread safe or not, we must reset it as false
+
+ MethodCallExpression methodCallExpression =
+ new MethodCallExpression(
+ attributeExpression,
+ CALL_STR,
+ argumentsExpr
+ );
+
+ return this.configureAST(methodCallExpression, ctx);
+ }
+
+ if (baseExpr instanceof PropertyExpression) { // e.g. obj.a(1, 2)
+ MethodCallExpression methodCallExpression =
+ this.createMethodCallExpression((PropertyExpression) baseExpr, argumentsExpr);
+
+ return this.configureAST(methodCallExpression, ctx);
+ }
+
+ if (baseExpr instanceof VariableExpression) { // void and primitive type AST node must be an instance of VariableExpression
+ String baseExprText = baseExpr.getText();
+ if (VOID_STR.equals(baseExprText)) { // e.g. void()
+ MethodCallExpression methodCallExpression =
+ new MethodCallExpression(
+ this.createConstantExpression(baseExpr),
+ CALL_STR,
+ argumentsExpr
+ );
+
+ methodCallExpression.setImplicitThis(false);
+
+ return this.configureAST(methodCallExpression, ctx);
+ } else if (PRIMITIVE_TYPE_SET.contains(baseExprText)) { // e.g. int(), long(), float(), etc.
+ throw createParsingFailedException("Primitive type literal: " + baseExprText + " cannot be used as a method name", ctx);
+ }
+ }
+
+ if (baseExpr instanceof VariableExpression
+ || baseExpr instanceof GStringExpression
+ || (baseExpr instanceof ConstantExpression && isTrue(baseExpr, IS_STRING))) { // e.g. m(), "$m"(), "m"()
+
+ String baseExprText = base
<TRUNCATED>