Tuesday, April 27, 2010

Bindings and Closures for script engine

So I've been working for a while to get the scripts that are run by the GroovyScriptEngine to have some more useful top level elements. In other words, I can write a script with the contents

command
and have something called "command" get run by the script. The very basic way to put things into the script's namespace is to call the GroovyScriptEngine.execute() command and pass it both the script you want to run and the Binding object. The binding object maps string names of things to values or objects which are available to the script. Setting variables in the binding to primitive values is easy but not hugely useful. Setting "thing" to "1" in the binding allows me to do things like

println thing


Not the most exciting. However, I've found that you can also set values in the binding to be more interesting things like a groovy Closure object. Since the code that is running the script and has access to the script engine itself is Java code I had to create an implementation of a Groovy Closure object, but once I did that I could set the closure to a variable in the binding and do things like

closure("argument")


where closure is the name of the binding variable that I set for the closure object. It actually passes the argument to the closure itself so you can call almost anything that way.

Right now I have the closure looking up existing JMXTerm commands and passing the remaining variables to them to run them. This is pretty cool. I can run all of the existing Java implemented commands as well as calling any of the scripts that I wrote that can be found, with all of the infinite loops that that might imply by calling a script from itself.

So this is useful but still kind of ugly.

I tried another tack to remove the necessity of the top level "closure" in order to call a command. I want the command to be at the top level in the script. To do this I tried to override the Binding object itself.

@Override
public Object getVariable(String name) {
try
{
return super.getVariable(name);
} catch (MissingPropertyException e)
{
Boolean callReturn = (Boolean) closure.call(new Object[] { name });
if (!callReturn)
{
throw e;
}
}

return null;
}


With this overridden method from Binding, when the script is called and it goes to its binding for a variable, if it can't find it I can have it call the closure I made before which already calls the built in commands. So I can do things like

command


and have "command" be run. It goes to the Binding.getVariable() method, catches the resulting MissingMethod exception and then passes it to the closure. The real downside here is that I can't seem to pass any variables to the command. If I do that I get a completely different exception from the bowels if Groovy's abstractcallSite object which I don't appear to have the opportunity to catch. Kinda lame. If the line has an argument it doesn't look it up in the Binding but tries to call it like a method on the script itself, which, of course, doesn't exist.

So I can either have a top level command with no arguments, or a wrapper method like "closure" or maybe a more friendly "run" that can take arguments but is ugly.

I think I'm going to leave this like this as I've spent too much time mucking around with this already. I'm going to start writing the scripts now and see what I need. I'll be my own customer from now on.

No comments:

Post a Comment