Jim Driscoll's Blog

Notes on Technology and the Web

Turtle Graphics DSL Implementation, part 1

leave a comment »

Last post, I described a Turtle Graphics DSL, as implemented in my sample program Napili.  (Please download it if you want to see the full code I’m discussing here).  This time, I’ll discuss how to implement it.  It’s surprisingly easy – though long enough that I’m going to tackle it in chunks, this is just Part 1.

First, to run the program, there’s the following snip of code inside a Java class (which is triggered when the user clicks the Run button):

Class clazz = Class.forName("org.netdance.napili.language.BasicRunner"); 
InvokerHelper.getMetaClass(clazz).invokeStaticMethod(clazz, "run", new Object[]{});

All this does is use Groovy’s dynamic invoker structures to invoke BasicRunner.run() – this code is only there so I can swap out the Groovy DSL classes at a later time, when I’ll discuss things like security.  I could have done the same thing with Java’s reflection classes, but if you already have Groovy in your class path, I heartily advise using InvokerHelper, there’s lots of handy stuff in there, and it’s considerably easier than using Java’s native reflection library.  One thing to be aware of, though, if you do:  it’s in the package org.codehaus.groovy.runtime, which means that the Groovy team reserves the right to change the APIs whenever they need to – which could make an upgrade of your Groovy version painful (though it’s not likely that InvokerHelper.getMetaClass(Class) is going to change anytime soon).

There are three classes that make up the language portion of the code.  The first, and by far easiest, is the BaseScript (you’ll remember, I hope, that I already discussed BaseScripts).  Written in Groovy, it becomes fairly trivial:

package org.netdance.napili.language
abstract class TurtleDelegateBaseScript extends Script {
    def methodMissing(String name, args) {
        turtle."$name"(*args)
    }
}

This uses a different method of Method Dispatch than I’ve previously discussed – he methodMissing method is called by Groovy whenever there’s no method found which matches a given name.  Since we don’t provide any methods at all, that means that pretty much every method called is going to go through this method.  (Since Scripts define other methods which will be inherited, like run and getBinding, and those won’t go through methodMissing, but that’s a problem for another day).  

The meat of the program is just a single line, in bold above.  I’ve already described what that kind of call does when I discussed Method Dispatch, but it’s worth going through one more time.  The first word, turtle, is interpreted as a Binding variable.   So the Groovy script goes into the Binding class (which I’ll describe shortly), and fetches the value corresponding to turtle.   Next, the “$name” is substituted with the method name called, which will then call the method name on the Turtle object.  Lastly, * is called the spread operator, and serves to break up the list of arguments into individual objects.   At runtime, a command like forward 100 will thus be transformed into something like: getBinding().getVariable("turtle").forward(100) – moving the turtle forward 100 steps.

The next portion is the BasicRunner itself – you’ll recall that we called the static public run method of that class, above.  It looks something like this:

static def run() {
  String scriptStr = Napili.code;
  def config = new CompilerConfiguration()
  config.setScriptBaseClass("org.netdance.napili.language.TurtleDelegateBaseScript")
  def ic = new ImportCustomizer();
  ic.addImports('javafx.scene.paint.Color')
  def ti = new ASTTransformationCustomizer([value: 15L],TimedInterrupt)
  config.addCompilationCustomizers(ic, ti)
  def shell = new GroovyShell(config)
  try {
    def script = shell.parse(scriptStr)
    script.setBinding(new BasicBinding())
    script.run()
  } catch (Exception e) {
    // error handling here
  }
}

In this method, first you fetch the script String to execute from the passed class.  Then, create a CompilerConfiguration object.  To the configuration object, set the BaseScript to the TurtleDelegateBaseScript just described, above.  Add a new import of javafx.scene.paint.Color, which will be applied to every script, so the user can say things like pencolor Color.PURPLE without having to do an import themselves. 

The next line, with it’s creation of an ASTTransformationCustomizer, looks a bit mysterious, but it’s effect is very straightforward – it sets a timeout on the script of 15 seconds.  After that, the script will throw an exception.  How it does this is a bit of an involved explanation, but we’ll have a lot more to say about AST Tranformations in a future post.

Then, as I’ve previously covered, just create a GroovyShell, parse the script, add a Binding (as I’ve also previously covered), and run the script.  If the user makes an error in the program, it’ll be caught as an Exception in the catch block.

The Binding itself isn’t terribly complicated either, but this is getting long, so I’ll continue on to describe the Binding section in the next post.

Advertisements

Written by jamesgdriscoll

October 13, 2012 at 1:38 PM

Posted in DSL, Groovy

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: