<< Useless Exception Messages | Home | Groovy is NOT a scripting language >>

A Groovy AST Viewer

I try spend an hour or so every day on 'other code'. At the moment I am creating a Groovy AST viewer for Eclipse. The main purpose is to be able to browse AST nodes and get an idea of the internals of a Groovy class. Being able to quickly see how code is represented in the AST is very helpful when looking for nodes suitable to help with code completion, amongst other things.

Every time I use Groovy my brain expands. I think differently. I think freely. Freely because I know I won't be beaten down by syntax if thinking outside the box. It's a great feeling to think of something, and then just do it without thinking so hard about how to do it.

The implementation of the AST viewer plugin is unlike any Java code you will see. Mostly because it is 70% Groovy code, and that 70% is practically impossible to implement in Java. A masochist might try, but would end up with some scary looking code indeed.

So what does the AST viewer look like? Here is a snapshot of my progress:


The entire structure is defined in Groovy code. The text labels are Groovy. The icons are generated using a mostly Groovy script. The branching logic is Groovy. The Java code forms a skeleton which holds the pieces together. Java as glue code - now you've heard it all!

The fun bit is that there is only one TreeNode class to represent all the nodes of the AST. The implementation, rather than being a programming problem, feels more like a configuration task.

There are many upsides:

  1. It prevents class explosion! I don't like wading through 30 classes where only 5 of them are the core of the program, the other 25 being specialization. This is a common side effect of Groovy - simply less classes.
  2. Less steps to get things done. Adding new nodes is trivial as you will see in code samples below. Open the file, insert code, done.
  3. Functionality is grouped. All code for getting children of a node are in one file. Text label makers are in another. And so on. Not a typical way to structure code, but it just feels right. This is the part that feels like configuration instead of programming. The existing code also makes for a handy reference when adding new nodes.
  4. The code is incredibly terse and readable (assuming you are familiar with Groovy and its so called Default Groovy Methods.

 

Here is the text label code.

[
(ClassNode) : { "${it.name}" },
(Expression) : { "${it.class.simpleName}" },
(FieldNode) : { "${it.name}" },
(MethodNode) : { "${it.name}" },
(Object) : { it instanceof org.codehaus.groovy.ast.ASTNode ? it.text : it.toString() },
(Parameter) : { "${it.name} : ${it.type.name}" },
(PropertyNode) : { "${it.field.name}" },
]

 

As you can see, it is simply a mapping of classes to closures which generate a text label for the tree nodes.

Here is how children of a node are retrieved:

// Creates an array of TreeNode[]. The method ignores null list elements.
def createTreeNodeArray(parent, list) {
return (TreeNode[])list
.findAll { it != null }
.collect { new TreeNode(parent, it) }
}

//Sort alphabetically for easy reference!
// Maps some class to a closure == { TreeNode -> }
[
(ClassNode) : { parent ->
def l = ["fields", "properties", "methods"].collect { parent.astObject[it] }
createTreeNodeArray(parent, l.flatten())
},

(Exceptions) : { parent -> createTreeNodeArray(parent, parent.astObject.list) },

(FieldNode) : { parent ->
def l = [ parent.astObject.type, "dynamic:${parent.astObject.dynamicTyped}".toString(), parent.astObject.initialValueExpression ]
createTreeNodeArray(parent, l)
},

(MethodNode) : { parent ->
def astObject = parent.astObject
createTreeNodeArray(parent, [
astObject.exceptions?.length > 0 ? new Exceptions(astObject.exceptions.toList()) : null,
astObject.parameters?.length > 0 ? new Parameters(astObject.parameters.toList()) : null
])
},

(ModuleNode) : { parent ->
def astNode = parent.astObject
def packageName = astNode.packageName
packageName = packageName ? packageName = new Package("package ${packageName[0..-2]}") : null
def imports = astNode.importPackages.collect { new ImportPackage("import ${it}*") }
def l = [packageName, imports, astNode.imports, astNode.methods, astNode.classes].flatten()
createTreeNodeArray(parent, l)
},

(Object) : { parent -> noChildren },

(Parameters) : { parent -> createTreeNodeArray(parent, parent.astObject.list) },

(PropertyNode) : { parent ->
def l = ["field", "getterBlock", "setterBlock"].collect { parent.astObject[it] }
createTreeNodeArray(parent, l)
},
]

 

Imagine the above code in Java. Now imagine not seeing it for 3 months and then coming back. I always find I get up to speed with my old Groovy code much more quickly - there is simply less to read and no need to mentally remove syntax fluff.

I haven't listed any Java code. It is rather trivial, involving mapping some sort of AST object to a closure and then executing the closure. The AST viewer will make it into the GroovyEclipse repository in a week or two for anyone that wants to see the details.




Add a comment Send a TrackBack