The obvious way to make Lua interwork with language L is to implement the Lua API in L, but this is daunting for the implementor, and burdens the L programmer with a verbose syntax. A simpler solution is to implement just lua_open
, lua_close
, lua_dobuffer
and lua_register
in L, expanding lua_register
to do inter-language marshalling. Additional functionality can then be provided in Lua.
The Lua API allows a Lua state to be completely controlled from C. But what if you want to interwork with another language, L? The obvious approach, assuming that L can interwork with C, is to reflect the C API into L. However, this is a rather daunting prospect, as the Lua API is quite large and has some hidden subtleties. Also, while it is a good medium for writing extension libraries and tools for Lua, it does not lead naturally to a convenient syntax for L programmers; the Lua manual shows (see section 5.12 on p. 26 in the 4.0 edition) how the Lua statement
a,b = f("how", t.x, 4)
becomes ten calls to the Lua API.
There is an easier way: using the first rule of Lua ("Do it in Lua"):
lua_dostring(S, "a,b = f(\"how\", t.x, 4)");
where S
is the state in which the code is to be executed. In fact, the only things you can't do with lua_dostring
are get values back from Lua, and allow Lua to call L. These can both be achieved with lua_register
.
Hence all that is needed for language interworking is lua_dostring
and lua_register
, plus lua_open
and lua_close
to allow Lua states to be created and destroyed. Also, it's better to use lua_dobuffer
than lua_dostring
, as it can handle pre-compiled code too.
But wait! In the C API, lua_register
says nothing about the argument or result types of the function being registered; these have to be dealt with by inspection and manipulation of the Lua stack. A brutal but simple solution to this is to make lua_register
specify the type and number of arguments and return values, and allow only such types as map naturally into L.
The final list of functions for the thin API is:
lua_open
and lua_close
, to allow Lua states to be created and destroyed
lua_dobuffer
, to allow Lua to be called from L
lua_register
(suitably specialised), to allow L to be called from Lua
When porting Lua to EPOC, Symbian's OS for mobile devices such as PDAs, I wanted to provide hooks to OS features such as the Eikon GUI. EPOC is C++-based, which looks promising, but for space reasons its libraries contain no symbol information, so run-time dynamic linking by name is impossible. Not wanting to resort to tolua, I decided instead to bind Lua to OPL, EPOC's interpreted BASIC-like RAD language, which has both good support for EPOC, including a wide range of OPXs (OPL libraries implemented in C++), and allows procedures to be called dynamically by name.
OPL has four basic types: 16-bit and 32-bit integers, 64-bit floats, and strings. 16-bit integers are denoted %
, 32-bit integers &
, strings $
, and floats by nothing. OPL supports C-like function prototypes, for example:
foo&:(a,b%,c$)
foo
is the name of the function. The &
indicates that it returns a 32-bit integer (all OPL functions return a value, which defaults to zero or the empty string if there is no explicit RETURN
statement). The colon indicates that foo
is a function. Next comes the optional argument list; in this case, there are three arguments: a float a
, a 16-bit integer b%
, and a string c$
. (Strings may be at most 255 characters long; in this API, longer strings may not be exchanged with Lua directly.)
Hence, I created a small OPX which provided the following OPL functions:
LuaOpen&:
returns a pointer to the new state
LuaClose:(state&)
closes the given state Lua&:(state&,chunk$)
executes the given chunk (which may be precompiled, but this is unlikely to be useful as it can be at most 255 bytes long) in the given state
LuaRegister:(state&,func$,name$)
registers the OPL function whose prototype is given by func$
in the given Lua state with Lua name name$
Lua&:
seemed a better name than LuaDoBuffer&:
as it is both apt (Lua&:
is the function that does some Lua) and a nice short name for what is likely to be the most widely used procedure by far out of the four. When an OPL function registered by LuaRegister:
is called from Lua, the arguments are automatically translated to the OPL types, and the result type translated back. It is the programmer's responsibility to check that integer arguments are in range.
At first sight, this interface may seem very limited. For example, there's no simple way to evaluate a Lua expression and return its result to OPL, nor is it possible to traverse Lua tables in OPL. This is intentional: adding these facilities would complicate the API, and omitting them encourages programmers to use OPL only to provide library routines to Lua. After all, the main motivation for linking Lua to OPL was to be able to aclearcase/" target="_blank" >ccess EPOC without needing to write lots of C++ libraries for Lua first.
However, in some cases I might want to write much of the application in the other language, because of its application domain properties (for example, SQL or Prolog). Also, I seem to be promoting Lua from its intended use as application extension language to the main language in which the application is written.
Actually, there is no conflict here. Think of Lua not so much as an application extension language as a glue language, binding bits of programs written in other languages together. The core of the application's functionality will often be implemented in some other language L, perhaps C for speed, or some domain-specific language. By structuring this core as a library, the L programmer is free to concentrate on providing application primitives in L, without worrying about tying them together; L may well not be suitable for this. The application can then be implemented as a layer of Lua on top of a series of libraries; this separates the different concerns of programming the domain-specific primitives in L from configuring the particular application, which makes the application easier to write, and promotes reuse of both Lua and L code.
If it is really necessary to implement other parts of the Lua API in L, then, provided it is not for performance reasons, the requisite functionality can still be implemented in Lua with L callbacks. Indeed, it would be possible to write a Lua implementation of the full Lua API which would then work with any language to which Lua was interfaced by the thin API.
Lua can be connected to other languages with a very simple API, which is mostly a subset of the standard C API. It is quick to implement, provided that the target language can interwork with C, and provides all the necessary functionality for writing applications in a mixture of Lua and the target language. Some seeming restrictions in the thin API actually help to write more reusable code.