Prerequisites
To run this workshop, you will need:
-
a
git
client -
an internet connection for the
Gradle Wrapper
-
or prepare yourself by downloading
Gradle 1.10
-
-
ideally, an IDE (but you can use any editor)
You should be able to get started only with Git installed:
git clone https://github.com/melix/ast-workshop.git cd ast-workshop git checkout exercise1 ./gradlew test
which should fail with:
:test org.gr8conf.AuthorAdderASTTransformationTest > testThatAuthorExists FAILED groovy.lang.MissingPropertyException at AuthorAdderASTTransformationTest.groovy:5 1 test completed, 1 failed :test FAILED
which is fine, this is our starting point. If you want IntelliJ IDEA support, just execute:
./gradlew idea
Following the workshop
The workshop consists of a series of exercises. We will give you explanations for each exercise and details about the solution can be found in a separate document. Don’t worry if you don’t complete an exercise in time, all solutions are available in separate git
branches. Exercises are self-contained, so if you checkout the branch of the exercise for each one, you should not have any problem, even if an exercise is the continuation of a previous one.
We may not have time to complete all exercises. Don’t worry. We’ve made this workshop accessible from beginners to more advanced Groovy hackers, so the time required to complete the workshop can vary. If you don’t finish all exercises in time, feel free to continue at home. You can still ask questions after the session, we would be pleased to answer. On the opposite, if you feel comfortable with the easiest exercises, you can start with more complex ones. |
Each exercise can be checked out issuing a git checkout
command:
git checkout exercise1
If you want to try your solution, you have two options. The first one is to run the unit test within your IDE (be careful with global AST transformations, you can have surprises) and the safest way is to use Gradle:
./gradlew test
Solutions can be found by checking out the same branch name, appended with -solution
:
git checkout exercise1-solution
It is recommanded that you save your solution first, to compare it with the workshop solutions:
git checkout -b exercise1-mysolution
git commit -a -m "My solution to exercise 1"
git checkout exercise1-solution
Exercises
Some exercises have optional questions. Those questions are more advanced and aimed towards people who have a deeper knowledge of AST transformations. For beginners, we recommand to skip them, but the solutions are included, so you can try to solve the problem later. |
Exercise 1: Global AST transformations
-
Create a global AST transformation that adds a public static final field
$AUTHOR
to all classes -
Initialize the field with your name
-
Update the unit test and make it pass
-
Can you use the AST transformation in the same source tree as the one it’s defined in?
-
(optional) Can you write a unit test which allows debugging the AST transformation (from your IDE for example)?
Should you need help, follow the instructions of this document |
Exercise 2: Discovering the AstBuilder
-
Modify the AST transformation so that implementation is in Groovy
-
Use the AST builder instead of creating the field "by hand"
-
What are the advantages of writing an AST transformation in Groovy?
-
Can you see any problem with writing the AST transform in Groovy?
-
What are the advantages of the AST builder?
-
What are the problems of the AST builder?
Should you need help, follow the instructions of this document |
Exercise 3: Local AST transformations
-
Remove the global AST transform descriptor file
-
Check that unit tests now fail
-
Introduce a
@Author
annotation -
Modify the transformation so that it now works on the annotated class
-
Check that the unit test still fails
-
Modify the unit test so that it passes
Should you need help, follow the instructions of this document |
Exercise 4: Parametrized AST transformations
-
Open the unit test and look at the argument to the
@Author
AST xform -
Add support for a parameter in the
@Author
AST xform -
(optional) add an error message if the AST transformation is triggered with wrong arguments
Should you need help, follow the instructions of this document |
Exercise 5: Using @ASTTest
-
Open the unit test and take a look at the
@ASTTest
annotation -
Fix the unit test so that you check that the class has an
$AUTHOR
field added -
Check that the field uses the
String
type -
Check that the field modifiers are
public
,static
andfinal
-
Check that the field has an initial expression which is
John Doe
-
What is the main difference between
@ASTTest
and the previous tests we’ve written? -
What solution is preferred?
Should you need help, follow the instructions of this document |
Exercice 6: ClassNode vs ClassHelper.make
-
Execute the unit test
-
Take a look at the behaviour of
makeWithoutCaching
. Is it surprising? Can you see what happens? -
How should you create class nodes in general?
-
Should you need help, follow the instructions of this document |
Exercise 7: Variable scope, @CompileStatic and hidden errors
The source code contains a global AST transformation that creates a sayHello
method that looks like this:
void sayHello(String message, boolean shout=false) {
println (shout?message.toUpperCase():message)
}
-
execute the unit test and check that it passes
-
uncomment the
@CompileStatic
annotation, run the test again. What happens?-
you are just facing an incompatibility with
@CompileStatic
, but what’s wrong?
-
-
execute the code again with
System.setProperty("groovy.stc.debug", "true")
-
What happens? What does it tell you?
-
-
at the end of the method body, add the following lines:
VariableScopeVisitor visitor = new VariableScopeVisitor(source);
visitor.prepareVisit(classNode);
visitor.visitMethod(method);
-
run the unit test again. What happens? What have you fixed?
Should you need help, follow the instructions of this document |
Exercise 8: Expression transformers
-
Look at the unit test. There’s a call to
nltnirp
which should be changed toprintln
using an AST transformation. -
Leaving the test untouched, implement a
ClassCodeExpressionTransformer
which performs the operation
Should you need help, follow the instructions of this document |
Exercise 9: Working with generics
The goal of this exercise is to transform fields of a simple type into a field of List<simple type>
.
-
run the unit test, check that it fails
-
in the transform code, change the type of the field to
ClassHelper.LIST_TYPE
. What happens? -
in the transform code, change the type of the field to
ClassHelper.LIST_TYPE.getPlainNodeReference()
. What happens? -
Update the unit test to check that:
-
the type of the field is now a
List
-
the generic type arguments of the field is a String type
-
-
If your unit test fails, fix the transform
Should you need help, follow the instructions of this document |
Exercise 10: Closures as annotation values
One of the most interesting features of annotations in Groovy is that they can have closures as values. The @ASTTest
transformation, for example, uses this feature, as well as AST transformations like @ConditionalInterrupt
or those found in GContracts.
-
open the
org.gr8conf.TransformArgument
annotation. Change the value type to the correct value so that closures can be used as arguments. -
open the AST transformation code and implement the
createMappingMethodCall
method-
Would it be easy to make it compatible with
@CompileStatic
? Why?
-
-
(optional) Can you make this annotation work with separate closures for each argument?
Should you need help, follow the instructions of this document |