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 2016/10/14 10:54:15 UTC

groovy git commit: Add groovy-macro documentation (closes #439)

Repository: groovy
Updated Branches:
  refs/heads/master 9e1a65e99 -> cff7a3c3d


Add groovy-macro documentation (closes #439)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/cff7a3c3
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/cff7a3c3
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/cff7a3c3

Branch: refs/heads/master
Commit: cff7a3c3d212f6d64058a427d34170bd30777fa2
Parents: 9e1a65e
Author: Mario Garcia <ma...@gmail.com>
Authored: Fri Oct 7 01:27:32 2016 +0200
Committer: paulk <pa...@asert.com.au>
Committed: Fri Oct 14 20:53:36 2016 +1000

----------------------------------------------------------------------
 build.gradle                                    |   2 +
 src/spec/doc/core-metaprogramming.adoc          | 237 ++++++++++++++++++-
 .../ASTMatcherFilteringTest.groovy              | 100 ++++++++
 .../ASTMatcherTestingTest.groovy                | 120 ++++++++++
 .../test/metaprogramming/MacroClassTest.groovy  | 101 ++++++++
 .../metaprogramming/MacroExpressionTest.groovy  |  92 +++++++
 .../metaprogramming/MacroStatementTest.groovy   | 123 ++++++++++
 .../MacroVariableSubstitutionTest.groovy        |  93 ++++++++
 8 files changed, 865 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index e82c4ab..fb340d9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -214,6 +214,7 @@ dependencies {
 
     examplesCompile project(':groovy-test')
     examplesCompile project(':groovy-swing')
+
     examplesCompile "org.apache.lucene:lucene-core:$luceneVersion"
     examplesCompile "org.apache.lucene:lucene-analyzers-common:$luceneVersion"
     examplesCompile "org.apache.lucene:lucene-queryparser:$luceneVersion"
@@ -238,6 +239,7 @@ dependencies {
 
     testCompile project(':groovy-ant')
     testCompile project(':groovy-test')
+    testCompile project(':groovy-macro')
 }
 
 ext.generatedDirectory = "${buildDir}/generated-sources"

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index c76ae30..52344fd 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -30,7 +30,7 @@ With runtime metaprogramming we can postpone to runtime the decision to intercep
 In Groovy we work with three kinds of objects: POJO, POGO and Groovy Interceptors. Groovy allows metaprogramming for all types of objects but in different manner.
 
 - POJO - A regular Java object, whose class can be written in Java or any other language for the JVM.
-- POGO - A Groovy object, whose class is written in Groovy. It extends `java.lang.Object` and implements the gapi:groovy.lang.GroovyObject[] interface by default. 
+- POGO - A Groovy object, whose class is written in Groovy. It extends `java.lang.Object` and implements the gapi:groovy.lang.GroovyObject[] interface by default.
 - Groovy Interceptor - A Groovy object that implements the gapi:groovy.lang.GroovyInterceptable[] interface and has method-interception capability, which we'll discuss in the <<core-metaprogramming.adoc#_groovyinterceptable,GroovyInterceptable>> section.
 
 For every method call Groovy checks whether the object is a POJO or a POGO. For POJOs, Groovy fetches it's `MetaClass` from the gapi:groovy.lang.MetaClassRegistry[] and delegates method invocation to it. For POGOs, Groovy takes more steps, as illustrated in the following figure:
@@ -79,7 +79,7 @@ Here is a simple example:
 ----
 include::{projectdir}/src/spec/test/metaprogramming/GroovyObjectTest.groovy[tags=groovy_get_property,indent=0]
 ----
-<1> Forwards the request to the getter for all properties except `field3`.  
+<1> Forwards the request to the getter for all properties except `field3`.
 
 You can intercept write access to properties by overriding the `setProperty()` method:
 
@@ -139,7 +139,7 @@ something like this:
 class GORM {
 
    def dynamicMethods = [...] // an array of dynamic methods that use regex
-   
+
    def methodMissing(String name, args) {
        def method = dynamicMethods.find { it.match(name) }
        if(method) {
@@ -2829,6 +2829,153 @@ to use the Groovy Console, in particular the AST browser tool, to gain knowledge
 resource for learning is the https://github.com/apache/groovy/tree/master/src/test/org/codehaus/groovy/ast/builder[AST Builder]
 test suite.
 
+==== Macros
+
+===== Introduction
+
+Until version 2.5.0, when developing AST transformations, developers should have a deep knowledge about how the AST
+(Abstract Syntax Tree) was built by the compiler in order to know how to add new expressions or statements during
+compile time.
+
+Although the use of `org.codehaus.groovy.ast.tool.GeneralUtils` static methods could mitigate the burden of creating
+expressions and statements, it's still a low-level way of writing those AST nodes directly.
+We needed something to abstract us from writing the AST directly and that's exactly what Groovy macros were made for.
+They allow you to directly add code during compilation, without having to translate the code you had in mind to the
+`org.codehaus.groovy.ast.*` node related classes.
+
+===== Statements and expressions
+
+Let's see an example, lets create a local AST transformation: `@AddMessageMethod`. When applied to a given class it
+will add a new method called `getMessage` to that class. The method will return "42". The annotation is pretty
+straight forward:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroStatementTest.groovy[tags=addmethodannotation,indent=0]
+----
+
+What would the AST transformation look like without the use of a macro ? Something like this:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroStatementTest.groovy[tags=addmethodtransformationwithoutmacro,indent=0]
+----
+
+<1> Create a return statement
+<2> Create a constant expression "42"
+<3> Adding the code to the new method
+<4> Adding the new method to the annotated class
+
+If you're not used to the AST API, that definitely doesn't look like the code you had in mind. Now look how the
+previous code simplifies with the use of macros.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroStatementTest.groovy[tags=basicWithMacro,indent=0]
+----
+
+<1> Much simpler. You wanted to add a return statement that returned "42" and that's exactly what you can read inside
+the `macro` utility method. Your plain code will be translated for you to a `org.codehaus.groovy.ast.stmt.ReturnStatement`
+<2> Adding the return statement to the new method
+<3> Adding the new code to the annotated class
+
+Although the `macro` method is used in this example to create a **statement** the `macro` method could also be used to create
+**expressions** as well, it depends on which `macro` signature you use:
+
+- `macro(Closure)`: Create a given statement with the code inside the closure.
+- `macro(Boolean,Closure)`: if **true** wrap expressions inside the closure inside an statement, if **false** then return
+an expression
+- `macro(CompilePhase, Closure)`: Create a given statement with the code inside the closure in a specific compile phase
+- `macro(CompilePhase, Boolean, Closure)`: Create an statement or an expression (true == statement, false == expression)
+in a specific compilation phase.
+
+NOTE: All these signatures can be found at `org.codehaus.groovy.macro.runtime.MacroGroovyMethods`
+
+Sometimes we could be only interested in creating a given expression, not the whole statement, in order to do that we
+should use any of the `macro` invocations with a boolean parameter:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroExpressionTest.groovy[tags=addgettwotransformation,indent=0]
+----
+
+<1> We're telling macro not to wrap the expression in a statement, we're only interested in the expression
+<2> Assigning the expression
+<3> Creating a `ReturnStatement` using a method from `GeneralUtils` and returning the expression
+<4> Adding the code to the new method
+<5> Adding the method to the class
+
+===== Variable substitution
+
+Macros are great but we can't create anything useful or reusable if our macros couldn't receive parameters or resolve
+surrounding variables.
+
+In the following example we're creating an AST transformation `@MD5` that when applied to a given String field will
+add a method returning the MD5 value of that field.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy[tags=md5annotation,indent=0]
+----
+
+And the transformation:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy[tags=md5transformation,indent=0]
+----
+
+<1> We need a reference to a variable expression
+<2> If using a class outside the standard packages we should add any needed imports or use the qualified name. When
+using the qualified named of a given static method you need to make sure it's resolved in the proper compile phase. In
+this particular case we're instructing the macro to resolve it at the SEMANTIC_ANALYSIS phase, which is the first compile phase
+with type information.
+<3> In order to substitute any `expression` inside the macro we need to use the `$v` method. `$v` receives a closure as an
+argument, and the closure is only allowed to substitute expressions, meaning classes inheriting
+`org.codehaus.groovy.ast.expr.Expression`.
+
+===== MacroClass
+
+As we mentioned earlier, the `macro` method is only capable of producing `statements` and `expressions`. But what if we
+want to produce other types of nodes, such as a method, a field and so on?
+
+`org.codehaus.groovy.macro.transform.MacroClass` can be used to create **classes** (ClassNode instances) in our
+transformations the same way we created statements and expressions with the `macro` method before.
+
+The next example is a local transformation `@Statistics`. When applied to a given class, it will add two methods
+**getMethodCount()** and **getFieldCount()** which return how many methods and fields within the class respectively. Here
+is the marker annotation.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroClassTest.groovy[tags=statisticsannotation,indent=0]
+----
+
+And the AST transformation:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/MacroClassTest.groovy[tags=statisticstransformation,indent=0]
+----
+
+<1> Creating a template class
+<2> Adding template class methods to the annotated class
+<3> Passing the reference class
+<4> Extracting reference class method count value expression
+<5> Extracting reference class field count value expression
+<6> Building the **getMethodCount()** method using reference's method count value expression
+<7> Building the **getFieldCount()** method using reference's field count value expression
+
+Basically we've created the **Statistics** class as a template to avoid writing low level AST API, then we
+copied methods created in the template class to their final destination.
+
+NOTE: Types inside the `MacroClass` implementation should be resolved inside, that's why we had to write
+`java.lang.Integer` instead of simply writing `Integer`.
+
+IMPORTANT: Notice that we're using `@CompileDynamic`. That's because the way we use `MacroClass` is like we
+were actually implementing it. So if you were using `@CompileStatic` it will complain because an implementation of
+an abstract class can't be another different class.
+
 ==== Testing AST transformations
 ===== Separating source trees
 
@@ -2868,6 +3015,90 @@ The difference is that when you use `assertScript`, the code in the `assertScrip
 unit test is executed*. That is to say that this time, the `Subject` class will be compiled with debugging active, and
 the breakpoint is going to be hit.
 
+===== ASTMatcher
+
+Sometimes you may want to make assertions over AST nodes; perhaps to filter the nodes, or to make sure a given
+transformation has built the expected AST node.
+
+**Filtering nodes**
+
+For instance if you would like to apply a given transformation only to a specific set of AST nodes, you could
+use **ASTMatcher** to filter these nodes. The following example shows how to transform a given expression to
+another. Using **ASTMatcher** it looks for a specific expression `1 + 1` and it transforms it to `3`. That's why
+we called it the `@Joking` example.
+
+First we create the `@Joking` annotation that only can be applied to methods:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy[tags=jokingannotation,indent=0]
+----
+
+Then the transformation, that only applies an instance of `org.codehaus.groovy.ast.ClassCodeExpressionTransformer`
+to all the expressions within the method code block.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy[tags=jokingtransformation,indent=0]
+----
+<1> Get the method's code statement and apply the expression transformer
+
+And this is when the **ASTMatcher** is used to apply the transformation only to those expressions matching
+the expression `1 + 1`.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy[tags=jokingexpressiontransformer,indent=0]
+----
+<1> Builds the expression used as reference pattern
+<2> Checks the current expression evaluated matches the reference expression
+<3> If it matches then replaces the current expression with the expression built with `macro`
+
+Then you could test the implementation as follows:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy[tags=jokingexample,indent=0]
+----
+
+**Unit testing AST transforms**
+
+Normally we test AST transformations just checking that the final use of the transformation does what we expect. But
+it would be great if we could have an easy way to check, for example, that the nodes the transformation adds are what
+we expected from the beginning.
+
+The following transformation adds a new method `giveMeTwo` to an annotated class.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy[tags=twiceasttransformation,indent=0]
+----
+<1> Adding the method to the annotated class
+<2> Building a binary expression. The binary expression uses the same variable expression in both
+sides of the `+` token (check `varX` method at **org.codehaus.groovy.ast.tool.GeneralUtils**).
+<3> Builds a new **ClassNode** with a method called `giveMeTwo` which returns the result of an expression
+passed as parameter.
+
+Now instead of creating a test executing the transformation over a given sample code. I would like to check that
+the construction of the binary expression is done properly:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy[tags=testexpression,indent=0]
+----
+<1> Using ASTMatcher as a category
+<2> Build a template node
+<3> Apply some constraints to that template node
+<4> Tells compiler that `a` is a placeholder.
+<5> Asserts reference node and current node are equal
+
+Of course you can/should always check the actual execution:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy[tags=executiontesting,indent=0]
+----
+
 ===== ASTTest
 
 Last but not least, testing an AST transformation is also about testing the state of the AST *during compilation*. Groovy

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy b/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy
new file mode 100644
index 0000000..5c40e5e
--- /dev/null
+++ b/src/spec/test/metaprogramming/ASTMatcherFilteringTest.groovy
@@ -0,0 +1,100 @@
+/*
+ *  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 metaprogramming
+
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.macro.matcher.ASTMatcher
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+class ASTMatcherFilteringTest extends GroovyTestCase {
+
+    void testFilteringNodes() {
+        assertScript '''
+        // tag::jokingexample[]
+        package metaprogramming
+
+        class Something {
+            @Joking
+            Integer getResult() {
+                return 1 + 1
+            }
+        }
+
+        assert new Something().result == 3
+        // end::jokingexample[]
+        '''
+    }
+}
+
+// tag::jokingannotation[]
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.METHOD])
+@GroovyASTTransformationClass(["metaprogramming.JokingASTTransformation"])
+@interface Joking { }
+// end::jokingannotation[]
+
+// tag::jokingtransformation[]
+@CompileStatic
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class JokingASTTransformation extends AbstractASTTransformation {
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        MethodNode methodNode = (MethodNode) nodes[1]
+
+        methodNode
+            .getCode()
+            .visit(new ConvertOnePlusOneToThree(source))  // <1>
+    }
+}
+// end::jokingtransformation[]
+
+// tag::jokingexpressiontransformer[]
+class ConvertOnePlusOneToThree extends ClassCodeExpressionTransformer {
+    SourceUnit sourceUnit
+
+    ConvertOnePlusOneToThree(SourceUnit sourceUnit) {
+        this.sourceUnit = sourceUnit
+    }
+
+    @Override
+    Expression transform(Expression exp) {
+        Expression ref = macro { 1 + 1 }     // <1>
+
+        if (ASTMatcher.matches(ref, exp)) {  // <2>
+            return macro { 3 }               // <3>
+        }
+
+        return super.transform(exp)
+    }
+}
+// end::jokingexpressiontransformer[]

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy b/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy
new file mode 100644
index 0000000..d86101f
--- /dev/null
+++ b/src/spec/test/metaprogramming/ASTMatcherTestingTest.groovy
@@ -0,0 +1,120 @@
+/*
+ *  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 metaprogramming
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.expr.BinaryExpression
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.macro.matcher.ASTMatcher
+import org.codehaus.groovy.macro.transform.MacroClass
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE
+import static org.codehaus.groovy.ast.tools.GeneralUtils.*
+
+class ASTMatcherTestingTest extends GroovyTestCase {
+
+    // tag::testexpression[]
+    void testTestingSumExpression() {
+        use(ASTMatcher) {                 // <1>
+            TwiceASTTransformation sample = new TwiceASTTransformation()
+            Expression referenceNode = macro {
+                a + a                     // <2>
+            }.withConstraints {           // <3>
+                placeholder 'a'           // <4>
+            }
+
+            assert sample
+                .sumExpression
+                .matches(referenceNode)   // <5>
+        }
+    }
+    // end::testexpression[]
+
+    // tag::executiontesting[]
+    void testASTBehavior() {
+        assertScript '''
+        package metaprogramming
+
+        @Twice
+        class AAA {
+
+        }
+
+        assert new AAA().giveMeTwo(1) == 2
+        '''
+    }
+    // end::executiontesting[]
+}
+
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.TYPE])
+@GroovyASTTransformationClass(["metaprogramming.TwiceASTTransformation"])
+@interface Twice { }
+
+// tag::twiceasttransformation[]
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class TwiceASTTransformation extends AbstractASTTransformation {
+
+    static final String VAR_X = 'x'
+
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode classNode = (ClassNode) nodes[1]
+        MethodNode giveMeTwo = getTemplateClass(sumExpression)
+            .getDeclaredMethods('giveMeTwo')
+            .first()
+
+        classNode.addMethod(giveMeTwo)                  // <1>
+    }
+
+    BinaryExpression getSumExpression() {               // <2>
+        return macro {
+            $v{ varX(VAR_X) } +
+            $v{ varX(VAR_X) }
+        }
+    }
+
+    ClassNode getTemplateClass(Expression expression) { // <3>
+        return new MacroClass() {
+            class Template {
+                java.lang.Integer giveMeTwo(java.lang.Integer x) {
+                    return $v { expression }
+                }
+            }
+        }
+    }
+}
+// end::twiceasttransformation[]

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/MacroClassTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/MacroClassTest.groovy b/src/spec/test/metaprogramming/MacroClassTest.groovy
new file mode 100644
index 0000000..818ce43
--- /dev/null
+++ b/src/spec/test/metaprogramming/MacroClassTest.groovy
@@ -0,0 +1,101 @@
+/*
+ *  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 metaprogramming
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.macro.transform.MacroClass
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
+
+class MacroClassTest extends GroovyTestCase {
+
+    void testMacroClass() {
+        assertScript '''
+        package metaprogramming
+
+        @Statistics
+        class Person {
+            Integer age
+            String name
+        }
+
+        def person = new Person(age: 12, name: 'john')
+
+        assert person.methodCount == 0
+        assert person.fieldCount  == 2
+        '''
+    }
+}
+
+// tag::statisticsannotation[]
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.TYPE])
+@GroovyASTTransformationClass(["metaprogramming.StatisticsASTTransformation"])
+@interface Statistics {}
+// end::statisticsannotation[]
+
+// tag::statisticstransformation[]
+@CompileStatic
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class StatisticsASTTransformation extends AbstractASTTransformation {
+
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode classNode = (ClassNode) nodes[1]
+        ClassNode templateClass = buildTemplateClass(classNode)  // <1>
+
+        templateClass.methods.each { MethodNode node ->          // <2>
+            classNode.addMethod(node)
+        }
+    }
+
+    @CompileDynamic
+    ClassNode buildTemplateClass(ClassNode reference) {          // <3>
+        def methodCount = constX(reference.methods.size())       // <4>
+        def fieldCount = constX(reference.fields.size())         // <5>
+
+        return new MacroClass() {
+            class Statistics {
+                java.lang.Integer getMethodCount() {             // <6>
+                    return $v { methodCount }
+                }
+
+                java.lang.Integer getFieldCount() {              // <7>
+                    return $v { fieldCount }
+                }
+            }
+        }
+    }
+}
+// end::statisticstransformation[]

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/MacroExpressionTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/MacroExpressionTest.groovy b/src/spec/test/metaprogramming/MacroExpressionTest.groovy
new file mode 100644
index 0000000..4f36ab1
--- /dev/null
+++ b/src/spec/test/metaprogramming/MacroExpressionTest.groovy
@@ -0,0 +1,92 @@
+/*
+ *  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 metaprogramming
+
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.expr.BinaryExpression
+import org.codehaus.groovy.ast.stmt.ReturnStatement
+import org.codehaus.groovy.ast.tools.GeneralUtils
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+class MacroExpressionTest extends GroovyTestCase {
+
+    void testCreateExpressions() {
+        assertScript '''
+        // add::addgettwosample[]
+        package metaprogramming
+
+        @AddGetTwo
+        class A {
+
+        }
+
+        assert new A().getTwo() == 2
+        // tag::addgettwosample[]
+        '''
+    }
+}
+
+// tag::addgettwoannotation[]
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.TYPE])
+@GroovyASTTransformationClass(["metaprogramming.AddGetTwoASTTransformation"])
+@interface AddGetTwo { }
+// end::addgettwoannotation[]
+
+// tag::addgettwotransformation[]
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class AddGetTwoASTTransformation extends AbstractASTTransformation {
+
+    BinaryExpression onePlusOne() {
+        return macro(false) { 1 + 1 }                                      // <1>
+    }
+
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode classNode = nodes[1]
+        BinaryExpression expression = onePlusOne()                         // <2>
+        ReturnStatement returnStatement = GeneralUtils.returnS(expression) // <3>
+
+        MethodNode methodNode =
+                new MethodNode("getTwo",
+                        ACC_PUBLIC,
+                        ClassHelper.Integer_TYPE,
+                        [] as Parameter[],
+                        [] as ClassNode[],
+                        returnStatement                                    // <4>
+                )
+
+        classNode.addMethod(methodNode)                                    // <5>
+    }
+}
+// end::addgettwotransformation[]

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/MacroStatementTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/MacroStatementTest.groovy b/src/spec/test/metaprogramming/MacroStatementTest.groovy
new file mode 100644
index 0000000..541341c
--- /dev/null
+++ b/src/spec/test/metaprogramming/MacroStatementTest.groovy
@@ -0,0 +1,123 @@
+/*
+ *  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 metaprogramming
+
+import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.stmt.ReturnStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+class MacroStatementTest extends GroovyTestCase {
+
+    void testOlderASTTransformation() {
+        assertScript '''
+        package metaprogramming
+
+        @AddMethod
+        class A {
+
+        }
+
+        new A().message  == '42'
+        '''
+    }
+
+    void testOlderASTTransformationWithMacros() {
+        assertScript '''
+        package metaprogramming
+
+        @AddMethodWithMacros
+        class A {
+
+        }
+
+        new A().message == '42'
+        '''
+    }
+}
+
+// tag::addmethodannotation[]
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.TYPE])
+@GroovyASTTransformationClass(["metaprogramming.AddMethodASTTransformation"])
+@interface AddMethod { }
+// end::addmethodannotation[]
+
+// tag::addmethodtransformationwithoutmacro[]
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class AddMethodASTTransformation extends AbstractASTTransformation {
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode classNode = (ClassNode) nodes[1]
+
+        ReturnStatement code =
+                new ReturnStatement(                              // <1>
+                        new ConstantExpression("42"))             // <2>
+
+        MethodNode methodNode =
+                new MethodNode(
+                        "getMessage",
+                        ACC_PUBLIC,
+                        ClassHelper.make(String),
+                        [] as Parameter[],
+                        [] as ClassNode[],
+                        code)                                     // <3>
+
+        classNode.addMethod(methodNode)                           // <4>
+    }
+}
+// end::addmethodtransformationwithoutmacro[]
+
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.TYPE])
+@GroovyASTTransformationClass(["metaprogramming.AddMethodWithMacrosASTTransformation"])
+@interface AddMethodWithMacros { }
+
+// tag::basicWithMacro[]
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+class AddMethodWithMacrosASTTransformation extends AbstractASTTransformation {
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode classNode = (ClassNode) nodes[1]
+
+        ReturnStatement simplestCode = macro { return "42" }   // <1>
+
+        MethodNode methodNode =
+                new MethodNode(
+                        "getMessage",
+                        ACC_PUBLIC,
+                        ClassHelper.make(String),
+                        [] as Parameter[],
+                        [] as ClassNode[],
+                        simplestCode)                          // <2>
+
+        classNode.addMethod(methodNode)                        // <3>
+    }
+}
+// end::basicWithMacro[]

http://git-wip-us.apache.org/repos/asf/groovy/blob/cff7a3c3/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy b/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy
new file mode 100644
index 0000000..b5be446
--- /dev/null
+++ b/src/spec/test/metaprogramming/MacroVariableSubstitutionTest.groovy
@@ -0,0 +1,93 @@
+/*
+ *  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 metaprogramming
+
+import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.ast.tools.GeneralUtils
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.AbstractASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+class MacroVariableSubstitutionTest extends GroovyTestCase {
+
+    void testVariableSubstitution() {
+        assertScript '''
+        package metaprogramming
+
+        class A {
+            @MD5
+            String word
+        }
+
+        def instance = new A(word: "Groovy")
+
+        assert instance.getWordMD5() == '2d19c57fdd4fdc270c971f69ee8d5169'
+        '''
+    }
+}
+// tag::md5annotation[]
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.FIELD])
+@GroovyASTTransformationClass(["metaprogramming.MD5ASTTransformation"])
+@interface MD5 { }
+// end::md5annotation[]
+
+// tag::md5transformation[]
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+class MD5ASTTransformation extends AbstractASTTransformation {
+
+    @Override
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        FieldNode fieldNode = nodes[1]
+        ClassNode classNode = fieldNode.declaringClass
+        String capitalizedName = fieldNode.name.capitalize()
+        MethodNode methodNode = new MethodNode(
+                "get${capitalizedName}MD5",
+                ACC_PUBLIC,
+                ClassHelper.STRING_TYPE,
+                [] as Parameter[],
+                [] as ClassNode[],
+                buildMD5MethodCode(fieldNode))
+
+        classNode.addMethod(methodNode)
+    }
+
+    BlockStatement buildMD5MethodCode(FieldNode fieldNode) {
+        VariableExpression fieldVar = GeneralUtils.varX(fieldNode.name) // <1>
+
+        return macro(CompilePhase.SEMANTIC_ANALYSIS, true) {            // <2>
+            return java.security.MessageDigest
+                    .getInstance('MD5')
+                    .digest($v { fieldVar }.getBytes())                 // <3>
+                    .encodeHex()
+                    .toString()
+        }
+    }
+}
+// end::md5transformation[]