Jim Driscoll's Blog

Notes on Technology and the Web

Using optional parenthesis in Groovy to your advantage

leave a comment »

Now that we’ve discussed adding default methods to your scripts with a BaseScript, it’s time to talk about one way to hide the fact that you’re calling a method:   Groovy allows you to omit parenthesis in cases where there is a parameter, and there is no ambiguity in use.

We can use this optional parens feature of Groovy to create more natural looking DSLs, and with judicious use of method chaining, we can create a simple grammar with very little code.  Let’s say that I want to create a simple DSL that moves a marker around in a confined space, and that the language should look similar to Logo, with a series of commands all in a line.  Since I don’t enjoy trigonometry,  I’ll forgo the usual two vector (x,y) space of Logo for a simpler single line.  That means than instead of needing to allow for commands like “forward 10 right 90 forward 6″, I can just use two verbs – “forward” and “back”.  We’ll add one more, “traveled”, to report total progress.  So, to define our DSL in a simple manner, I want to accept commands like “forward 10 forward 6 back 3 traveled”.

So, we can start by creating a simple BaseScript with the three verbs (let’s do it this time in Groovy, I’m feeling lazy):

 

abstract class BaseScript extends Script {

  int distance = 0;
    
  def forward(int i) {
    distance += i
    println "moved forward "+i
    return this
  }
    
  def back(int i) {
    distance -= i;
    println "moved back "+i
    return this
  }
    
  def traveled() {
    println "total distance traveled " + distance
    return this
  }

}

 

The script is storing the distance as an instance variable, and then all the methods are acting on that variable in some way.  Also, note that each method returns the current script object, enabling method chaining.

Once this is set up, we can use the following code to execute a script:

  CompilerConfiguration config = new CompilerConfiguration();
  config.setScriptBaseClass("groovybasescriptlogo.BaseScript");
  GroovyShell shell = new GroovyShell(config);
  Script script = shell.parse("forward 10 back 3 forward 5 traveled()");
  script.run();

 

The output when we run this is as you’d expect:

moved forward 10
moved back 3
moved forward 5
total distance traveled 12

 

But note there’s a catch here:  because traveled is a method without a parameter, there still needs to be a set of parenthesis at the end.  The reason why is also a clue as to how we can fix it.  Without the parenthesis, the traveled keyword will be interpreted as a binding variable instead of a method.  Since we’ve already covered how to handle Binding variables, we can change the code by using a Binding class.  First, eliminate the first bolded line in the BaseScript – that changes the distance variable from an instance variable on the Script class to a Binding variable.  You can also remove the traveled() method from the BaseScript as well, or leave it in there, to cover the case of using a parenthesis.  Then create a Binding class like so:

 

public class SimpleBinding extends Binding {
    
  int distance = 0;
  Script script;
    
  public SimpleBinding(Script script) {
    super();
    this.script = script;
  }

  public Object getVariable(String name) {
    if ("distance".equals(name)) {
      return distance;
    }
    if ("traveled".equals(name)) {
      System.out.println("total distance traveled " + distance);
      return script;
    }
    return super.getVariable(name);
    }
        
  public void setVariable(String name, Object value) {
    if ("distance".equals(name)) {
      distance = (Integer)value;
      return;
    }
    super.setVariable(name, value);
  }
    
}

 

So, the distance variable moves from the BaseScript to the Binding.  In the setVariable, add the ability to set the distance variable.  That covers the usage in the BaseScript.  We’ve also moved code around traveled to the Binding as well:  Now, when traveled is used without a parenthesis, it will go to the binding, print out the message, and still return the Script object (which is passed into the Binding at creation).  To use this code, just modify the calling code like so:

 

  CompilerConfiguration config = new CompilerConfiguration();
  config.setScriptBaseClass("groovybasescriptlogo.BaseScript");
  GroovyShell shell = new GroovyShell(config);
  Script script = shell.parse("forward 10 back 3 forward 5 traveled");
  script.setBinding(new SimpleBinding(script));
  script.run();

 

The output remains the same.  Doing this now eliminates the need for the trailing parenthesis on the traveled keyword.  That concludes this topic – feel free to ask any questions, below – I know this example is a little more involved than previous ones, but the concepts are still pretty simple, even if there’s more code on the page.  The next posting will cover a different way to handle adding verbs dynamically, via the invokeMethod mechanism.

 

About these ads

Written by jamesgdriscoll

April 29, 2012 at 1:48 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

Follow

Get every new post delivered to your Inbox.

Join 413 other followers

%d bloggers like this: