JSR-223 support in ABCL
ABCL is integrated with the Java Scripting API (JSR-223, package javax.script) which is built-in in Java 6.
This page describes the design decisions behind the ABCL JSR-223 support. It is not a description of what JSR-223 is or a tutorial on how to use it. See http://trac.common-lisp.net/armedbear/browser/trunk/abcl/examples/jsr-223 for example usage.
Implemented interfaces
JSR-223 defines three main interfaces, of which two (Invocable and Compilable) are optional. ABCL implements all the three interfaces - ScriptEngine and the two optional ones - almost completely. The JSR-223 API is not specific to a single scripting language, however was designed with languages with a more or less Java-like object model in mind: languages such as Javascript, Python, Ruby, which have a concept of "class" or "object" with "fields" and "methods". Lisp is a bit different, so certain adaptations were made, and in one case a method has been left unimplemented since it does not map at all to Lisp.
ScriptEngine
The main interface defined by JSR-223, javax.script.ScriptEngine
, is implemented by the class org.armedbear.lisp.scripting.AbclScriptEngine. AbclScriptEngine is intended to be a singleton, and as such it has a protected constructor. You can obtain an instance of AbclScriptEngine using the AbclScriptEngineFactory or by using the service provider mechanism through ScriptEngineManager (refer to the javax.script documentation).
Startup and configuration file
At startup (i.e. when its constructor is invoked, as part of the static initialization phase of AbclScriptEngineFactory) the ABCL script engine attempts to load an "init file" from the classpath (/abcl-script-config.lisp). If present, this file can be used to customize the behaviour of the engine, by setting a number of variables in the ABCL-SCRIPT package. Here is a list of the available variables:
*use-throwing-debugger*
Controls whether ABCL uses a non-standard debugging hook function to throw a Java exception instead of dropping into the debugger in case of unhandled error conditions.- Default value: T
- Rationale: it is more convenient for Java programmers using Lisp as a scripting language to have it return exceptions to Java instead of handling them in the Lisp world.
- Known Issues: the non-standard debugger hook has been reported to misbehave in certain circumstances, so consider disabling it if it doesn't work for you.
*launch-swank-at-startup*
If true, Swank will be launched at startup. See*swank-dir*
and*swank-port*
.- Default value: NIL
*swank-dir*
The directory where Swank is installed. Must be set if*launch-swank-at-startup*
is true.*swank-port*
The port where Swank will listen for connections. Must be set if*launch-swank-at-startup*
is true.- Default value: 4005
Additionally, at startup the AbclScriptEngine will (require 'asdf) - in fact, it uses asdf to load Swank.
Evaluation
Code is read and evaluated in the package ABCL-SCRIPT-USER. This packages USEs the COMMON-LISP, JAVA and ABCL-SCRIPT packages. Future versions of the script engine might make this default package configurable. The load function is used under the hood for evaluating code, and thus the same behavior of load is guaranteed. This allows, among other things, in-package forms to change the package in which the loaded code is read.
It is possible to evaluate code in what JSR-223 calls a "ScriptContext" (basically a flat environment of name->value pairs). This context is used to establish special bindings for all the variables defined in it; since variable names are strings from Java's point of view, they are first interned using READ-FROM-STRING with, as usual, ABCL-SCRIPT-USER as the default package. Variables are declared special because CL's load, eval and compile functions work in a null lexical environment and would ignore non-special bindings.
Contrary to what the function load does, evaluation of a series of forms returns the value of the last form instead of T.
Compilation
AbclScriptEngine implements the javax.script.Compilable
interface. Currently as of 2009-05-11 it only supports compilation using temporary files. It used to support compilation in-memory using the Lisp runtime compiler, but it had some issues - its behavior could not be assimilated to the temp-file-based in all cases, and thus support for it has been dropped. Compiled code, returned as a javax.script.CompiledScript
, is read, compiled and executed by default in the ABCL-SCRIPT-USER package, like evaluated code. Differently from evaluated code, though, due to the way the ABCL compiler works, compiled code contains no reference to top-level self-evaluating objects (like numbers or strings). Thus, when evaluated, a piece of compiled code will return the value of the last non-self-evaluating form: for example the code "(do-something) 42" will return 42 when interpreted, but will return the result of (do-something) when compiled and later evaluated. To ensure consistency of behavior between interpreted and compiled code, make sure the last form is always a compound form - at least (identity some-literal-object). Note that this issue should not matter in real code, where it is unlikely a top-level self-evaluating form will appear as the last form in a file (in fact, the Common Lisp load function always returns T upon success; with JSR-223 this policy has been changed to make evaluation of small code snippets feasible).
Invocation of functions and methods
AbclScriptEngine implements the javax.script.Invocable
interface. This interface allows to directly call functions and methods in the script, as well as to get implementations of Java interfaces from the script. This is only partially possible with Lisp since it has functions, but not methods - not in the traditional OO sense, at least, since Lisp methods are not attached to objects but belong to generic functions. Thus, the invokeMethod() is not implemented and throws an UnsupportedOperationException when called. The invokeFunction() method should be used to call both regular and generic functions.
Implementation of Java interfaces
ABCL can use the Java reflection-based proxy feature to implement Java interfaces in Lisp. It has several built-in ways to implement an interface, and supports definition of new ones. The jmake-proxy generic function is used to make such proxies. It has the following signature:
jmake-proxy interface implementation &optional lisp-this ==> proxy
interface is a Java interface metaobject or a string naming a Java interface. implementation is the object used to implement the interface - several built-in methods of jmake-proxy exist for various types of implementations. lisp-this is an object passed to the closures implementing the Lisp "methods" of the interface.
The returned proxy is an instance of the interface, with methods implemented with Lisp functions.
Built-in interface-implementation types include:
- a single Lisp function which upon invocation of any method in the interface will be passed the method name, the Lisp-this object, and all the parameters. Useful for interfaces with a single method, or to implement custom interface-implementation strategies.
- an hash-map of method-name -> Lisp function mappings. Function signature is (lisp-this &rest args).
- a Lisp package. The name of the Java method to invoke is first transformed in an idiomatic Lisp name (javaMethodName becomes JAVA-METHOD-NAME) and a symbol with that name is searched in the package. If it exists and is fbound, the corresponding function will be called. Function signature is as the hash-table case.
This functionality is exposed by the AbclScriptEngine with the two methods getInterface(Class) and getInterface(Object, Class). The former returns an interface implemented with the current Lisp package, the latter allows the programmer to pass an interface-implementation object which will in turn be passed to the jmake-proxy generic function.