cl-kawa
Common Lisp / Kawa Scheme interop via OpenLDK.
Kawa Scheme, created by Per Bothner, compiles Scheme code to Java bytecode and runs on the JVM. OpenLDK is a JVM implemented in Common Lisp; it transpiles Java bytecode to Common Lisp code. In SBCL, that Common Lisp code is then compiled to native assembly. Since both share the same SBCL process and heap, cl-kawa enables deep interoperability between Common Lisp and Scheme with no serialization or process boundaries.
Project status: technology demonstration. Not a performant or production ready implementation.
What you can do
- Evaluate Scheme from Common Lisp (strings or s-expressions).
- Call Scheme procedures from Common Lisp.
- Register Common Lisp functions and call them from Scheme.
- Exchange basic values (numbers, strings, booleans, lists) across the boundary.
Prerequisites
- SBCL
- OpenLDK
- Java 8 JDK (
JAVA_HOMEpointing to a JRE withlib/rt.jar) - Kawa 3.1.1 JAR (downloaded automatically
via Maven, or manually placed in
libs/)
Install
- Ensure prerequisites are installed and
JAVA_HOMEis set. - Download Kawa (or let Maven fetch it) into
libs/askawa-3.1.1.jar. - Load the ASDF system from your Common Lisp image.
Quick start
(asdf:load-system :cl-kawa) ;; Initialize the runtime (point to the Kawa JAR) (kawa:startup :classpath "libs/kawa-3.1.1.jar") ;; Evaluate Scheme expressions -- as s-expressions or strings (kawa:eval '(+ 1 2)) ; => 3 (kawa:eval '(string-length "hello")) ; => 5 (kawa:eval '(list 1 2 3)) ; => (1 2 3) (kawa:eval "(* 6 7)") ; => 42 (strings still work) ;; Look up a Scheme procedure and call it from CL (let ((add (kawa:lookup "+"))) (kawa:funcall add 10 20)) ; => 30 ;; Register a CL function so Scheme can call it (kawa:register "cl-square" (lambda (x) (* x x))) (kawa:eval '(cl-square 7)) ; => 49
Hello World: three languages in one process
hello.lisp demonstrates the full Common Lisp → Scheme → Java
interop chain in a single SBCL process:
(format t "~A~%" (kawa:eval '(let ((s (|java.lang.String| (string-append "Hello" ", " "World!")))) (|s:toUpperCase|)))) ;; prints: HELLO, WORLD!
This single expression crosses three language boundaries:
- Common Lisp calls
kawa:evalwith a quoted s-expression. - Kawa Scheme assembles the greeting with
string-appendand wraps it in ajava.lang.String. - Java's
String.toUpperCase()runs -- its bytecode was transpiled by OpenLDK into Common Lisp, then compiled by SBCL to native x86-64 assembly.
The result flows back through Scheme's value system into Common Lisp
and is printed by format. No FFI, no sockets, no serialization --
just nested function calls inside a single Lisp image.
LDK_CLASSPATH=libs/kawa-3.1.1.jar \ JAVA_HOME=/path/to/java8/jre \ sbcl --load hello.lisp
Environment variables
JAVA_HOME: path to a Java 8 JRE that containslib/rt.jar.LDK_CLASSPATH: optional classpath override for OpenLDK.
API
(kawa:startup &key classpath)
Initialize the Kawa Scheme runtime. classpath is a colon-separated
string of JAR paths. Safe to call multiple times (subsequent calls are
no-ops).
(kawa:eval expr &optional env) => value
Evaluate a Scheme expression. expr can be a string of Scheme source
or a Common Lisp s-expression (symbols are automatically downcased,
quote becomes ', t/nil become #t/'()). Returns the result
converted to a Common Lisp value.
(kawa:lookup name &optional env) => object
Look up a Scheme binding by name. Returns the raw Java/Kawa object
(typically a Procedure).
(kawa:funcall proc &rest args) => value
Call a Scheme procedure with Common Lisp arguments. Arguments are automatically converted to Kawa values; the result is converted back.
(kawa:register name function &optional env)
Register a Common Lisp function as a Scheme procedure. The function
becomes callable from Scheme code via kawa:eval.
(kawa:scheme->cl obj) => value
Convert a Java/Kawa object to a Common Lisp value. Handles integers, floats, strings, booleans, and lists. Returns the object unchanged if no conversion applies.
(kawa:cl->scheme val) => object
Convert a Common Lisp value to a Java/Kawa object. Handles integers, floats, strings, and cons cells.
(kawa:make-environment &optional parent) => environment
Create a new Scheme environment, optionally inheriting from a parent.
Value conversions
| Kawa type | Common Lisp type |
|---|---|
gnu.math.IntNum |
integer |
gnu.math.DFloNum |
double-float |
java.lang.String |
string |
java.lang.Boolean |
T / NIL |
gnu.lists.Pair |
cons |
gnu.lists.LList (empty) |
NIL |
Running the tests
Or manually:
LDK_CLASSPATH=libs/kawa-3.1.1.jar \ JAVA_HOME=/path/to/java8/jre \ sbcl --load test.lisp
Limitations and notes
- Designed as a proof-of-concept; performance and completeness are not goals.
- The conversion layer only handles basic scalar and list types.
- Requires Java 8 because OpenLDK depends on
rt.jar.
Author and License
cl-kawa was written by Anthony Green and is distributed under the terms of the MIT license.