/* * 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.codehaus.groovy.macro.methods; import groovy.lang.Closure; import groovy.lang.DelegatesTo; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.tools.ClosureUtils; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.macro.runtime.Macro; import org.codehaus.groovy.macro.runtime.MacroBuilder; import org.codehaus.groovy.macro.runtime.MacroContext; import org.codehaus.groovy.syntax.SyntaxException; import java.util.Iterator; import static org.codehaus.groovy.ast.tools.GeneralUtils.*; public class MacroGroovyMethods { public static final String DOLLAR_VALUE = "$v"; public static class MacroValuePlaceholder { public static Object $v(Closure cl) { // replaced with AST transformations return null; } } public static <T> T macro(Object self, @DelegatesTo(MacroValuePlaceholder.class) Closure cl) { throw new IllegalStateException("MacroGroovyMethods.macro(Closure) should never be called at runtime. Are you sure you are using it correctly?"); } @Macro public static Expression macro(MacroContext macroContext, ClosureExpression closureExpression) { return macro(macroContext, new ConstantExpression(false, true), closureExpression); } public static <T> T macro(Object self, boolean asIs, @DelegatesTo(MacroValuePlaceholder.class) Closure cl) { throw new IllegalStateException("MacroGroovyMethods.macro(boolean, Closure) should never be called at runtime. Are you sure you are using it correctly?"); } @Macro public static Expression macro(MacroContext macroContext, ConstantExpression asIsConstantExpression, ClosureExpression closureExpression) { return macro(macroContext, null, asIsConstantExpression, closureExpression); } public static <T> T macro(Object self, CompilePhase compilePhase, @DelegatesTo(MacroValuePlaceholder.class) Closure cl) { throw new IllegalStateException("MacroGroovyMethods.macro(CompilePhase, Closure) should never be called at runtime. Are you sure you are using it correctly?"); } @Macro public static Expression macro(MacroContext macroContext, PropertyExpression phaseExpression, ClosureExpression closureExpression) { return macro(macroContext, phaseExpression, new ConstantExpression(false, true), closureExpression); } public static <T> T macro(Object self, CompilePhase compilePhase, boolean asIs, @DelegatesTo(MacroValuePlaceholder.class) Closure cl) { throw new IllegalStateException("MacroGroovyMethods.macro(CompilePhase, boolean, Closure) should never be called at runtime. Are you sure you are using it correctly?"); } @Macro public static Expression macro(MacroContext macroContext, PropertyExpression phaseExpression, ConstantExpression asIsConstantExpression, ClosureExpression closureExpression) { if (closureExpression.getParameters() != null && closureExpression.getParameters().length > 0) { macroContext.getSourceUnit().addError(new SyntaxException("Macro closure arguments are not allowed" + '\n', closureExpression)); return macroContext.getCall(); } final String source; try { source = ClosureUtils.convertClosureToSource(macroContext.getSourceUnit().getSource(), closureExpression); } catch (Exception e) { throw new RuntimeException(e); } BlockStatement closureBlock = (BlockStatement) closureExpression.getCode(); Boolean asIs = (Boolean) asIsConstantExpression.getValue(); return callX( propX(classX(ClassHelper.makeWithoutCaching(MacroBuilder.class, false)), "INSTANCE"), "macro", args( phaseExpression != null ? phaseExpression : constX(null), asIsConstantExpression, constX(source), buildSubstitutions(macroContext.getSourceUnit(), closureExpression), classX(ClassHelper.makeWithoutCaching(MacroBuilder.getMacroValue(closureBlock, asIs).getClass(), false)) ) ); } public static ListExpression buildSubstitutions(final SourceUnit source, final ASTNode expr) { final ListExpression listExpression = new ListExpression(); ClassCodeVisitorSupport visitor = new ClassCodeVisitorSupport() { @Override protected SourceUnit getSourceUnit() { return null; } @Override public void visitClass(final ClassNode node) { super.visitClass(node); Iterator<InnerClassNode> it = node.getInnerClasses(); while (it.hasNext()) { InnerClassNode next = it.next(); visitClass(next); } } @Override public void visitMethodCallExpression(MethodCallExpression call) { super.visitMethodCallExpression(call); if (DOLLAR_VALUE.equals(call.getMethodAsString())) { ClosureExpression substitutionClosureExpression = getClosureArgument(source, call); if (substitutionClosureExpression == null) { return; } Statement code = substitutionClosureExpression.getCode(); if (code instanceof BlockStatement) { ((BlockStatement) code).setVariableScope(null); } listExpression.addExpression(substitutionClosureExpression); } } }; if (expr instanceof ClassNode) { visitor.visitClass((ClassNode) expr); } else { expr.visit(visitor); } return listExpression; } protected static TupleExpression getMacroArguments(SourceUnit source, MethodCallExpression call) { Expression macroCallArguments = call.getArguments(); if (macroCallArguments == null) { source.addError(new SyntaxException("Call should have arguments" + '\n', call)); return null; } if (!(macroCallArguments instanceof TupleExpression)) { source.addError(new SyntaxException("Call should have TupleExpression as arguments" + '\n', macroCallArguments)); return null; } TupleExpression tupleArguments = (TupleExpression) macroCallArguments; if (tupleArguments.getExpressions() == null) { source.addError(new SyntaxException("Call arguments should have expressions" + '\n', tupleArguments)); return null; } return tupleArguments; } protected static ClosureExpression getClosureArgument(SourceUnit source, MethodCallExpression call) { TupleExpression tupleArguments = getMacroArguments(source, call); if (tupleArguments == null || tupleArguments.getExpressions().size() < 1) { source.addError(new SyntaxException("Call arguments should have at least one argument" + '\n', tupleArguments)); return null; } Expression result = tupleArguments.getExpression(tupleArguments.getExpressions().size() - 1); if (!(result instanceof ClosureExpression)) { source.addError(new SyntaxException("Last call argument should be a closure" + '\n', result)); return null; } return (ClosureExpression) result; } }