| 1 | Lisp FFI |
|---|
| 2 | ======== |
|---|
| 3 | |
|---|
| 4 | Mark Evenson |
|---|
| 5 | Created: 15-FEB-2010 |
|---|
| 6 | Modified: 18-MAR-2010 |
|---|
| 7 | |
|---|
| 8 | FFI stands for "Foreign Function Interface", which is the way the |
|---|
| 9 | contemporary Lisp world refers to methods of "calling out" from Lisp |
|---|
| 10 | into "foreign" langauges and envrionments. This document describes |
|---|
| 11 | the various ways that one interacts with Lisp world of Abcl from Java, |
|---|
| 12 | considering the hosted Lisp as the "Foreign Function" that needs to be |
|---|
| 13 | "Interfaced". |
|---|
| 14 | |
|---|
| 15 | # Lisp FFI |
|---|
| 16 | |
|---|
| 17 | ## Calling Lisp from Java |
|---|
| 18 | |
|---|
| 19 | Note: As the entire ABCL Lisp system resides in the org.armedbear.lisp |
|---|
| 20 | package the following code snippets do not show the relevant import |
|---|
| 21 | statements in the interest of brevity. |
|---|
| 22 | |
|---|
| 23 | Per JVM, there can only ever be a single Lisp interpreter. This is |
|---|
| 24 | started by calling the static method `Interpreter.createInstance()`. |
|---|
| 25 | |
|---|
| 26 | Interpreter interpreter = Interpreter.createInstance(); |
|---|
| 27 | |
|---|
| 28 | If this method has already been invoked in the lifetime of the current |
|---|
| 29 | Java process it will return null, so if you are writing Java whose |
|---|
| 30 | lifecycle is a bit out of your control (like in a Java servlet), a |
|---|
| 31 | safer invocation pattern might be: |
|---|
| 32 | |
|---|
| 33 | Interpreter interpreter = Interpreter.getInstance(); |
|---|
| 34 | if (interpreter == null) { |
|---|
| 35 | interpreter = Interpreter.createInstance(); |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | The Lisp `EVAL` primitive may be simply passed strings for evaluation, |
|---|
| 39 | as follows |
|---|
| 40 | |
|---|
| 41 | String line = "(load \"file.lisp\")"; |
|---|
| 42 | LispObject result = interpreter.eval(line); |
|---|
| 43 | |
|---|
| 44 | Notice that all possible return values from an arbitrary Lisp |
|---|
| 45 | computation are collapsed into a single return value. Doing useful |
|---|
| 46 | further computation on the `LispObject` depends on knowing what the |
|---|
| 47 | result of the computation might be, usually involves some amount |
|---|
| 48 | of instanceof introspection, and forms a whole topic to itself |
|---|
| 49 | (c.f. [Introspecting a LispObject](#introspecting)). |
|---|
| 50 | |
|---|
| 51 | Using `EVAL` involves the Lisp interpreter. Lisp functions may be |
|---|
| 52 | directly invoked by Java method calls as follows. One simply locates |
|---|
| 53 | the package containing the symbol, then obtains a reference to the |
|---|
| 54 | symbol, and then invokes the `execute()` method with the desired |
|---|
| 55 | parameters. |
|---|
| 56 | |
|---|
| 57 | interpreter.eval("(defun foo (msg) (format nil \"You told me '~A'~%\" msg))"); |
|---|
| 58 | Package pkg = Packages.findPackage("CL-USER"); |
|---|
| 59 | Symbol foo = pkg.findAccessibleSymbol("FOO"); |
|---|
| 60 | Function fooFunction = (Function)foo.getSymbolFunction(); |
|---|
| 61 | JavaObject parameter = new JavaObject("Lisp is fun!"); |
|---|
| 62 | LispObject result = fooFunction.execute(parameter); |
|---|
| 63 | // How to get the "naked string value"? |
|---|
| 64 | System.out.prinln("The result was " + result.writeToString()); |
|---|
| 65 | |
|---|
| 66 | If one is calling an primitive function in the CL package the syntax |
|---|
| 67 | becomes considerably simpler if we can locate the instance of |
|---|
| 68 | definition in the ABCL source, we can invoke the symbol directly. To |
|---|
| 69 | tell if a `LispObject` contains a reference to a symbol. |
|---|
| 70 | |
|---|
| 71 | boolean nullp(LispObject object) { |
|---|
| 72 | LispObject result = Primitives.NULL.execute(object); |
|---|
| 73 | if (result == NIL) { |
|---|
| 74 | return false; |
|---|
| 75 | } |
|---|
| 76 | return true; |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | <a name="interpreting"/> |
|---|
| 80 | ## Introspecting a LispObject |
|---|
| 81 | |
|---|
| 82 | We present various patterns for introspecting an an arbitrary |
|---|
| 83 | `LispObject` which can represent the result of every Lisp evaluation |
|---|
| 84 | into semantics that Java can meaniningfully deal with. |
|---|
| 85 | |
|---|
| 86 | ### LispObject as boolean |
|---|
| 87 | |
|---|
| 88 | If the LispObject a generalized boolean values, one can use |
|---|
| 89 | `getBooleanValue()` to convert to Java: |
|---|
| 90 | |
|---|
| 91 | LispObject object = Symbol.NIL; |
|---|
| 92 | boolean javaValue = object.getBooleanValue(); |
|---|
| 93 | |
|---|
| 94 | Although since in Lisp, any value other than NIL means "true", the |
|---|
| 95 | use of Java equality it quite a bit easier and more optimal: |
|---|
| 96 | |
|---|
| 97 | boolean javaValue = (object != Symbol.NIL); |
|---|
| 98 | |
|---|
| 99 | ### LispObject is a list |
|---|
| 100 | |
|---|
| 101 | If LispObject is a list, it will have the type `Cons`. One can then use |
|---|
| 102 | the `copyToArray[]` to make things a bit more suitable for Java |
|---|
| 103 | iteration. |
|---|
| 104 | |
|---|
| 105 | LispObject result = interpreter.eval("'(1 2 4 5)"); |
|---|
| 106 | if (result instanceof Cons) { |
|---|
| 107 | LispObject array[] = ((Cons)result.copyToArray()); |
|---|
| 108 | ... |
|---|
| 109 | } |
|---|
| 110 | |
|---|
| 111 | A more Lispy way to iterated down a list is to use the `cdr()` access |
|---|
| 112 | function just as like one would traverse a list in Lisp:; |
|---|
| 113 | |
|---|
| 114 | LispObject result = interpreter.eval("'(1 2 4 5)"); |
|---|
| 115 | while (result != Symbol.NIL) { |
|---|
| 116 | doSomething(result.car()); |
|---|
| 117 | result = result.cdr(); |
|---|
| 118 | } |
|---|
| 119 | |
|---|