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