Embedding APIs of Java-Based Scripting Engines

Most of the discussions on scripting languages tend to be focused on the syntax, or sometimes the performance comparison. However, it is also important to know how scripting engines are integrated into applications.

Scripting engine is said to be embeddable if it provides an explicitly documented API, so that it can be integrated into Java applications. And such API is called an embedding API of scripting language.

This document investigates embedding APIs of selected scripting engines, focusing on how the following common tasks are implemented.

Evaluate an Expression Reading from a String

Jacl
Interp interp = new Interp();
Object result = interp.eval("1+2");
Beanshell
Interpreter interp = new Interpreter();
Object result = interp.exec("1+2");
Jython
PythonInterpreter interp = new PythonInterpreter();
PyObject result = interp.eval("1+2");
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  Object result = cx.evaluateString(scope, "1+2", "", 1, null);
} finally {
  cx.exit();
}
Groovy
GroovyShell gs = new GroovyShell();
Object result = gs.evaluate("1+2");
Pnuts
Context context = new Context();
Object result = Pnuts.eval("1+2", contenxt);

Execute a Script Reading from Stream (without creating String)

Jacl
Not available
Beanshell
Interpreter interp = new Interpreter();
Object result = interp.eval(reader);
Jython
PythonInterpreter interp = new PythonInterpreter();
interp.execfile(inputStream);
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  Object result = cx.evaluateReader(scope, reader, "", 1, null);
} finally {
  cx.exit();
}
Groovy
GroovyShell gs = new GroovyShell();
Object result = gs.evaluate(in);
Pnuts
Context context = new Context();
Object result = Pnuts.load(reader, contenxt);

Catch an Exception Thrown by a Script

Jacl
Interp interp = new Interp();
try {
  Object result = interp.eval("1+2");
} catch (TclException e){
  int code = e.getCompletionCode();
}
Beanshell
Interpreter interp = new Interpreter();
try {
  Object result = interp.exec(...);
} catch (EvalError e){
  String text = e.getErrorText();
  int line = e.getErrorLineNumber();
  String file = e.getErrorSourceFile();
  String trace = e.getScriptStackTrace();
  String msg = e.getMessage();
}
Jython
PythonInterpreter interp = new PythonInterpreter();
try {
  interp.execfile(file);
} catch (PyException e){
  PyObject type = e.type;
  PyObject value = e.value;
  PyTraceback traceback = e.traceback;
}
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  Object result = cx.evaluateReader(scope, reader, "", 1, null);
} catch (JavaScriptException e){
  Object value = e.getValue();
  String source = e.getSourceName();
  int line = e.getLineNumber();
} finally {
  cx.exit();
}
Groovy
try {
  GroovyShell gs = new GroovyShell();
  Object result = gs.evaluate("1+2");
} catch (GroovyRuntimeException e0){
  String location = e0.getLocationText();
} catch (SyntaxException e1){
} catch (ClassNotFoundException e2){
} catch (IOException e3){
}
Pnuts
try {
  Context context = new Context();
  Pnuts.eval("...", context);
} catch (PnutsException e){
  Throwable t = e.getThrowable();
}

Set/Get a Variable Without Parsing

Jacl
Interp interp = new Interp();
interp.setVar("foo", "stringValue", TCL.GLOBAL_ONLY);
interp.getVar("foo", TCL.GLOBAL_ONLY);
Beanshell
Interpreter interp = new Interpreter();
interp.set("a", new Integer(1));
Object result = interp.get("a");
Jython
PythonInterpreter interp = new PythonInterpreter();
interp.set("a", new Integer(1));
PyObject result = interp.get("a");
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  scope.put("symbol", scope, new Integer(1));
  Object result = scope.get("symbol", scope);
} finally {
  cx.exit();
}
Groovy
GroovyShell gs = new GroovyShell();
Binding b = gs.getContext();
b.setVariable("a", new Integer(1));
Object result = b.getVariable("a");
Pnuts
Package pkg = context.getCurrentPackage();
String symbol = "symbol".intern();
pkg.set(symbol, new Integer(1));
Object value = pkg.get(symbol);

Call a function/method without parsing scripts

Jacl
Command cmd = interp.getCommand(cmdName);
TclObject[] args = ...;
cmd.cmdProc(interp, args);
Beanshell
Interpreter interp = new Interpreter();
NameSpace namespace = interp.getNameSpace();
BshMethod cmd = (BshMethod)namespace.getCommand("cmdname", new Class[]{...}, interp);
Object result = cmd.invoke(new Object[]{...}, interp);
Jython
PythonInterpreter interp = new PythonInterpreter();
interp.exec("def foo():\r\n  0;");
PyFunction f = (PyFunction)interp.get("foo");
PyObject result = f.__call__();
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  Function func = ...;
  Object result = func.call(cx, scope, func, new Object[]{args, ...});
} finally {
  cx.exit();
}
Groovy
GroovyShell gs = new GroovyShell();
Closure closure = (Closure)gs.evaluate("Object.hashCode");
closure.call();
Pnuts
Context context = new Context();
Object result = func.call(new Object[]{...}, context);

Parse a Script Without Executing

Jacl
Not available 
Beanshell
Parser parser = new Parser(input);
while(!(eof = parser.Line())) {
 parser.popNode();
}
// This usage is just for syntax checking
// No public interface to execute the AST
Jython
Not documented.
Rhino
Rhino always compiles scripts
Groovy
Groovy always compiles scripts
Pnuts
Context context = new Context();
try {
  Pnuts pn = Pnuts.parse(input, "", context);
  pn.run(context);
} catch (ParseException e){
  int line = e.getErrorLine();
  int column = e.getErrorColumn();
  String token = e.currentToken.image;
}

Compile a Script Without Executing

Jacl
Compiler is not available.
Beanshell
Not documented.
Jython
Not documented.
Rhino
Context cx = Context.enter();
try {
  Scriptable scope = cx.initStandardObjects();
  Script compiled = scope.compileReader(reader, "", 1, null);
//  Object result = compiled.exec(cx, scope);
} finally {
  cx.exit();
}
Groovy
GroovyShell gs = new GroovyShell();
Script script = gc.parse(input);
// Object result = script.run();
Pnuts
Context context = new Context();
Compiler compiler = new Compiler();
Pnuts pn = Pnuts.parse(input);
try {
   pn = compiler.compile(pn, context);
} catch (ClassFormatError e){
   // exceeds JVM limitation
}
//Object result = pn.run(context);

Reference