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 | |
---|