Lua Common Lisp. An implementation of Common Lisp targeting Lua.
Go to file
gsou c11ad3a835 Random 2023-08-31 18:04:04 -04:00

Lua Common Lisp is an implementation of Common Lisp targeting the Lua language. The goal of this project is to provide an implementation of Common Lisp that can be used wherever an unmodified Lua VM is running.

LCL is being tested on Lua 5.4.4 and LuaJIT 2.1.0-beta3.

LCL is not a Common Lisp to Lua compiler. LCL has a Common Lisp to Lua compiler. LCL is an implementation of Common Lisp that happens to be running on Lua.

Getting started

Download lcl.lua from the releases.

Load lcl.lua in your Lua implementation:

lua -i lcl.lua
luajit -i lcl.lua

And in Lua, run Common Lisp code:

eval('(compile-file #P"lisp-code.lisp")')
eval("(mapcar (lambda (x y) (* x (+ y 2))) '(1 2 3) '(#C(0.5 0.5) 17 82.5))'")

A lisp repl in lcl-repl.lua is provided from version 0.5.

rlwrap luajit -i lcl-repl.lua

Incompatibilities with Common Lisp

LCL is currently being developped and is not a complete Common Lisp implementation.

Most functions and macros still work differently than specified by the standard or are currently missing or incomplete. The main missing features are:

  • No bignum
  • No pretty printer

Any incompatibilities are considered bugs.

Bootstrapping process

Running the script will bootstrap LCL. The bootstrap process is as follows:

  1. Compile every lisp file from the stdlib folder to lua from SBCL. As SBCL is not LCL, some lua optimizations are either not possible or inconvenient to perform and as such the LCL implementation produced by this step is slow. The result of this step is the lcl_sbcl.lua file.
  2. Compile every lisp file from the stdlib folder to lua from LCL (lcl_sbcl.lua) The results are aggregated into the lcl.lua file.
  3. Recompile lcl.lua using itself.
  4. Recompile lcl.lua using itself, along with the extensions.

Note that bootstrapping is not required to compile LCL, as the already compiled lcl.lua file is available from the releases.

To compile LCL from LCL:

-- Load LCL produced LCL
-- Compile all stdlib files
eval('(load "boot/lcl-bootstrap.lisp")')

And collect the lua files:


LCL interaction with Lua


After loading lcl.lua, the Lua VM is ready to run Common Lisp code. To do that, the lisp functions can be directly called by Lua or by the host application by following the calling convention.

Note that the symbol metatable provides a __call metamethod so symbols can be called directly.

For instance, to read a form from a string, the lisp function read-from-string is used. It is located in the COMMON-LISP package. It can be accessed by: LCL['COMMON-LISP']['READ-FROM-STRING'], or with the shortcut CL['READ-FROM-STRING']. This function takes the string to read, two optional arguments and key arguments. If we want to omit any optional or key arguments, the function is called as follows:

read_from_string = CL['READ-FROM-STRING']("read-from-string", nil, nil, {})

read-from-string returns a symbol. It is assigned to the Lua global read_from_string in the snippet above. Since symbols are callable, we could do:

CL.PRINT(read_from_string("#C(0 1)", nil, nil, {}))
-- Prints: #C(0 1)

Or we could just use cl:funcall if we don't wan't to care about the calling convention.

CL.PRINT( CL.FUNCALL( read_from_string, l( "#C(0 1)" ) ) )
-- Prints: #C(0 1)

The read-from-string function returns a Lisp list. The eval function evaluates a Lisp list. They can be used in the same way from Lua:

CL.PRINT(CL.EVAL(CL.FUNCALL( CL['READ-FROM-STRING'], l("(reduce #'cons '(1 2 3 4) :from-end t)"))))
-- Prints: (1 2 3 . 4)

The Lisp list could also be generated from Lua:

           l(CL.REDUCE, l(CL.FUNCTION, CL.CONS), l(CL.QUOTE, l(1,2,3,4)), KEYWORD['FROM-END'], CL.T)))
-- Prints: (1 2 3 . 4)

-- Or even
           {CL.REDUCE, {{CL.FUNCTION, {CL.CONS, CL.NIL}}, { {CL.QUOTE, {{1,{2,{3,{4, CL.NIL}}}}, CL.NIL}}, {KEYWORD['FROM-END'], {CL.T, CL.NIL}}}}}))
-- Prints: (1 2 3 . 4)

The load and compile-file are ways to load Lisp code from files as in other Common Lisp implementations.

CL.EVAL(CL['READ-FROM-STRING']('(compile-file #P"lispcode.lisp")', nil, nil, {}))
CL.EVAL(CL['READ-FROM-STRING']('(load #P"lispcode.lisp")', nil, nil, {}))

Additionally, some Lua functions are included to easily call Common Lisp.

function eval(str) ... end
function lclc(str) ... end
function read(str) ... end

These functions all take a string containing a Lisp form. If the argument is ommited, cl:read is called and a form is read from stdin.

The function read reads the form and prints it.

The function lclc reads the form, compile it to Lua and return a string containing the resulting Lua code.

The function eval reads the form, evaluates it, and prints it. The result of the evaluation is returned.

-- From stdin: (reduce #'cons '(1 2 3 4) :from-end t)
-- Prints: (1 2 3 . 4)
-- Return a table containing the above list

-- Equivalent, without reading from stdin:
eval("(reduce #'cons '(1 2 3 4) :from-end t)")

lclc("(reduce #'cons '(1 2 3 4) :from-end t)")
-- Returns:  return CL["REDUCE"](CL["CONS"].fbound, {1,{2,{3,{4,CL["NIL"]}}}},
--                               k(CL_LIB["FROM-END"], CL["T"].bound))

read("(reduce #'cons '(1 2 3 4) :from-end t)")
-- Prints: (REDUCE (FUNCTION CONS) (QUOTE (1 2 3 4)) :FROM-END T)

read("#.(reduce #'cons '(1 2 3 4) :from-end t)")
-- Prints: (1 2 3 . 4)


Two additional special forms are provided to directly generate lua code from LCL.

Multiple macros to manipulate Lua objects are also defined in stdlib/native.lisp

(lua &rest forms)

The forms are compiled to lua and then concatenated. The exception is for any string element in forms, which is directly considered Lua code.

Here are a few examples:

(let ((list '(1 2 3))) (lua list "[1]"))
local l1 = {1,{2,{3,CL["NIL"]}}};
return l1[1]
-- Returns: 1

Note that the fact that lua interprets all strings as raw code can cause some confusion:

(defmacro something-with-lua (arg) `(lua "some_lua_global[" ,arg "]"))

(something-with-lua "a string")
 return some_lua_global[a string] -- Oops

Here's a way that generates the expected code:

(defmacro something-with-lua (arg)
  (let ((arg-sym (gensym)))
    `(let ((,arg-sym ,arg)) (lua "some_lua_global[" ,arg-sym "]"))))

(something-with-lua "a string")
local l1 = "a string"; return some_lua_global[l1]

(lua-push (&rest pushed-forms) expr)

First, pushed-forms are pushed to the current context. Then the lisp expression expr is returned.

The elements of pushed-forms are parsed in the same way as the forms of a lua special form.

This special form is mainly useful to create Lua statements, since normally in LCL every Lisp expression matches to a Lua expression during compilation.

  (defun rplaca (cons object) (lua-push (" " cons "[1] = " object) object))
  (defun rplacd (cons object) (lua-push (" " cons "[2] = " object) object)))
     function (l1, l2,  ...)   l1[1] = l2 return l2 end)) );
     function (l3, l4,  ...)   l3[2] = l4 return l4 end) )

LCL Datatypes

All Lisp objects can be matched to a Lua object.


Symbols are stored in a table of the following format:

{ package = PACKAGE, name = STRING }

Symbols have the metatable cl-lib:symbol-metatable (or CL_LIB['SYMBOL-METATABLE'] in Lua)


Lists are stored in a table of the following format:

{ [1] = CAR, [2] = CDR }
-- or

These cons cells have a nil metatable (Lua's nil).

For instance, the Lisp list '(1 2 3 4) corresponds to the Lua expression {1, {2, {3, {4, CL.NIL}}}}.


Integers and floats use the internal Lua number type.

53 + eval('(* 0.5 2)') -- 54

Rational numbers are stored in a table of the following format:

{ n = numerator_integer, d = denominator_integer }

Rational numbers have the metatable cl-lib:rational-metatable (or CL_LIB["RATIONAL-METATABLE"] in Lua)

Complex numbers are stored in a table of the following format:

{ r = real_part, i = imag_part }

Complex numbers have the metatable cl-lib:complex-metatable (or CL_LIB["COMPLEX-METATABLE"] in Lua)

Bignums are not implemented in LCL.


Characters are stored in a table of the following format:

{ char = SINGLE_CHAR_STRING, code = CODE }

Characters have the metatable cl-lib:char-metatable (or CL_LIB['CHAR-METATABLE'] in Lua).

The char field of the table contains a Lua string containing a single character.


Strings are Lua strings.

"Sample " .. eval('"String"') -- "Sample String"


Arrays are stored in a table of the following format:

  dim = DIMENSION,
  default = INITIAL_ELEMENT,
  offset = OFFSET,
  data = DATA

Arrays have the metatable cl-lib:array-metatable (or CL_LIB['ARRAY-METATABLE'] in Lua)


Hash-Tables are stored in a table of the following format:

{ data = {} }

The data field is a lua table whose key/value entries correspond to the entry in the hash-table. Additionally the hash-table has the metatable cl-lib:hash-table-metatable (or CL_LIB['HASH-TABLE-METATABLE'] in Lua)

Note that since the hash-table uses lua's indexing, the only index test type corresponds closest to #'equal.


Structures are stored in a table of the following format:


Additional key value pairs in the table are the slots of the structures, where the key is the symbol representing the slot name.

Structures have the metatable cl-lib:struct-metatable (or CL_LIB['STRUCT-METATABLE'] in Lua)


Lisp functions are stored in a table of the following format:

{ arglist = ARGLIST, func = LUA_FUNCTION }

Functions have the metatable cl-lib:function-metatable (or CL_LIB['FUNCTION-METATABLE'] in Lua). This metatable has a __call metamethod.


Lisp macros are stored in a table of the following format:

{ macro_function = LISP_FUNCTION_TABLE }

Macros have the metatable cl-lib:macro-metatable (or CL_LIB['MACRO-METATABLE'] in Lua). This metatable has a __call metamethod.


Packages are stored in a table of the following format:


Additional key value pairs in the table contain the symbols inside of the package. These symbols are created automatically on index if they do not yet exist. The package has the metatable cl-lib:package-metatable (or CL_LIB['PACKAGE-METATABLE'] in Lua).

LCL Calling Convention

Lua functions matching Lisp functions have the following calling convention:

Arglist Lua
(a b c) f(a, b, c)
(a &optional b c) f(a)
f(a, nil, nil)
f(a, b, c)
(a &key b c) f(a, k(PACKAGE.B, b, PACKAGE.C,c ))
f(a, { [PACKAGE.B] = b, [PACKAGE.C] = c })
f(a, { [PACKAGE.C] = c })
f(a, {})
(a &rest b) f(a, l(remaining, elements))

Positional arguments are positional arguments in the Lua function.

Optional arguments are optional arguments in the Lua function. The called function is responsible of setting its default values if the &optional (or &key) argument is not supplied, is nil (Lua's nil) or false.

If a &rest argument is required, A Lua argument taking a Lisp list of the remaining arguments is added at the end of the positional and optional arguments.

If the function has &key arguments, an additional Lua argument taking a table of key/values is added at the end of the positional, optional and rest argument. The key of this table is the corresponding symbol (not the keyword) in the arglist

The l and k functions are provided to generate &rest arguments or &key arguments, respectively. The &rest and &key arguments equivalent in the Lua function are not optional, and must be supplied.

Any &aux in the arglist is managed by the called function.