Enlightment seekers solve metakoans by filling __
and __()
placeholders. They were originally added to each class involved in koan asserts (luckily, there were only two of them). An example of such a class:
class Bike { final __ = 'fill_me_in' def __() { 'fill_me_in' } }
As exercise, I moved them to a Groovy AST transformation. I wanted just to annotate a class with @KoanPlaceholders
and let the compiler add required methods and fields. Here is the final code of my experiment:
Annotation:
@Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass(['transform.KoanPlaceholdersAstTransformation']) public @interface KoanPlaceholders { }
Transformation implementation:
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class KoanPlaceholdersAstTransformation implements ASTTransformation { private static final FILL_ME_IN = new ConstantExpression('fill_me_in') private static final OBJECT = new ClassNode(Object) private static final NO_EXCEPTIONS = [] as ClassNode[] private static final NO_PARAMS = [] as Parameter[] private static final NO_VARIABLE_SCOPE = null @Override void visit(ASTNode[] nodes, SourceUnit sourceUnit) { if (!nodes) return if (nodes.size() < 2) return if (!(nodes[0] instanceof AnnotationNode)) return if (!(nodes[1] instanceof ClassNode)) return ClassNode classNode = nodes[1] classNode.addMethod(buildMethodPlaceholder()) classNode.addField(buildFieldPlaceholder(classNode)) } private buildMethodPlaceholder() { def methodBody = new BlockStatement([new ReturnStatement(FILL_ME_IN)], NO_VARIABLE_SCOPE) new MethodNode('__', ACC_PUBLIC, OBJECT, NO_PARAMS, NO_EXCEPTIONS, methodBody) } private buildFieldPlaceholder(owner) { new FieldNode('__', ACC_PUBLIC | ACC_FINAL, OBJECT, owner, FILL_ME_IN) } }
Bike
after:
@KoanPlaceholders class Bike { }
Notes:
- You have to put the transformation annotation and implementation in a package (don’t work if in the default package)
KoanPlaceholdersAstTransformation
works if koans are run from the command line (gant), but not from my IDE (IntelliJ Idea). I suppose compilation order is important.- Writing a transformation by hand (weaving manually different kinds of
ASTNode
s) is easier than you think. If you’re beginner, keep your hands offAstBuilder
‘sbuildFromString
/buildFromCode
/buildFromSpec
. You may think theAstBuilder
will sweep the problems under the rug; however, in case of a mis-transformation, you don’t know to deal with it (either you get a compiler exception or simply the transformation doesn’t work). Patching the AST transformation together using the low level API, you learn how the abstract syntax tree is constructed by the Groovy compiler. - Load a class into the Groovy console and go to Script/Inspect Ast to learn how AST is built and which nodes are used
And remember:
AST transformations are not scary, give’em a try!