I'm tasked with testing the call processing on “Project: Wolowizard.” M suggested, and I concurred, that using Lua [1] to manage the testing scripts would be a Good Thing™. Easier to write and modify the tests as needed. So over the past few years I've written a number of modules to handle the files and protocols used in the project (one side effect: by re-implemeting the code to read/write the various data files helped to verify the specification and flush out architectural dependencies in the binary formats).
But one problem did exist: Not all the systems I need to run the test on have Lua installed, and LuaRocks [2] has … um … “issues” on our Solaris boxes (otherwise, it's not that bad a package manager [3]). So I decided to build what I call “Kitchen Sink Lua”—a Lua interpreter that has the 47 modules required to run the testing scripts (okay, eight of the modules are already built into Lua).
It took some time to wrangle, as some of the modules were written in Lua (so the source needed to be embedded) and I had to figure out how to integrate some third party modules (like LuaCURL [4]) into the build system, but perhaps the hardest bit was to ensure the modules were initialized properly. My first attempt, while it worked (mostly by accident) wasn't technically correct (as I realized when I read this message on a mailing list [5]).
I then restructured my code, which not only made it correct, but smaller and clearer.
>
```
#include <stdlib.h>
#include <assert.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/**************************************************************************/
typedef struct prelua_reg
{
const char *const name;
const char *const code;
const size_t *const size;
} prelua_reg__t;
/*************************************************************************/
int luaopen_org_conman_env (lua_State *);
int luaopen_org_conman_errno (lua_State *);
int luaopen_org_conman_fsys (lua_State *);
int luaopen_org_conman_math (lua_State *);
int luaopen_org_conman_syslog (lua_State *);
int luaopen_org_conman_hash (lua_State *);
int luaopen_org_conman_string_trim (lua_State *);
int luaopen_org_conman_string_wrap (lua_State *);
int luaopen_org_conman_string_remchar (lua_State *);
int luaopen_org_conman_process (lua_State *);
int luaopen_org_conman_net (lua_State *);
int luaopen_org_conman_dns (lua_State *);
int luaopen_org_conman_sys (lua_State *);
int luaopen_org_conman_uuid (lua_State *);
int luaopen_lpeg (lua_State *);
int luaopen_LuaXML_lib (lua_State *);
int luaopen_cURL (lua_State *);
/***********************************************************************/
/*---------------------------------------------------------------
; Modules written in Lua. The build system takes the Lua code,
; processes it through luac (the Lua compiler), then creates an
; object file which exports a character array containing the byte
; code, and a variable which gives the size of the bytecode array.
;---------------------------------------------------------------*/
extern const char c_org_conman_debug[];
extern const size_t c_org_conman_debug_size;
extern const char c_org_conman_getopt[];
extern const size_t c_org_conman_getopt_size;
extern const char c_org_conman_string[];
extern const size_t c_org_conman_string_size;
extern const char c_org_conman_table[];
extern const size_t c_org_conman_table_size;
extern const char c_org_conman_unix[];
extern const size_t c_org_conman_unix_size;
extern const char c_re[];
extern const size_t c_re_size;
extern const char c_LuaXml[];
extern const size_t c_LuaXml_size;
/*----------------------------------------------------------------
; Modules written in C. We can use luaL_register() to load these
; into package.preloaded[]
;----------------------------------------------------------------*/
const luaL_Reg c_preload[] =
{
{ "org.conman.env" , luaopen_org_conman_env } ,
{ "org.conman.errno" , luaopen_org_conman_errno } ,
{ "org.conman.fsys" , luaopen_org_conman_fsys } ,
{ "org.conman.math" , luaopen_org_conman_math } ,
{ "org.conman.syslog" , luaopen_org_conman_syslog } ,
{ "org.conman.hash" , luaopen_org_conman_hash } ,
{ "org.conman.string.trim" , luaopen_org_conman_string_trim } ,
{ "org.conman.string.wrap" , luaopen_org_conman_string_wrap } ,
{ "org.conman.string.remchar" , luaopen_org_conman_string_remchar } ,
{ "org.conman.process" , luaopen_org_conman_process } ,
{ "org.conman.net" , luaopen_org_conman_net } ,
{ "org.conman.dns" , luaopen_org_conman_dns } ,
{ "org.conman.sys" , luaopen_org_conman_sys } ,
{ "org.conman.uuid" , luaopen_org_conman_uuid } ,
{ "lpeg" , luaopen_lpeg } ,
{ "LuaXML_lib" , luaopen_LuaXML_lib } ,
{ "cURL" , luaopen_cURL } ,
{ NULL , NULL }
};
/*---------------------------------------------------------------
; Modules written in Lua. These need to be loaded and populated
; into package.preloaded[] by some code provided in this file.
;----------------------------------------------------------------
const prelua_reg__t c_luapreload[] =
{
{ "org.conman.debug" , c_org_conman_debug , &c_org_conman_debug_size } ,
{ "org.conman.getopt" , c_org_conman_getopt , &c_org_conman_getopt_size } ,
{ "org.conman.string" , c_org_conman_string , &c_org_conman_string_size } ,
{ "org.conman.table" , c_org_conman_table , &c_org_conman_table_size } ,
{ "org.conman.unix" , c_org_conman_unix , &c_org_conman_unix_size } ,
{ "re" , c_re , &c_re_size } ,
{ "LuaXml" , c_LuaXml , &c_LuaXml_size } ,
{ NULL , NULL , NULL }
};
/*************************************************************************/
void preload_lua(lua_State *const L)
{
assert(L != NULL);
lua_gc(L,LUA_GCSTOP,0);
luaL_openlibs(L);
lua_gc(L,LUA_GCRESTART,0);
/*---------------------------------------------------------------
; preload all the modules. This does does not initialize them,
; just makes them available for require().
;
; I'm doing it this way because of a recent email on the LuaJIT
; email list:
;
; http://www.freelists.org/post/luajit/Trivial-bug-in-bitops-bitc-luaopen-bit,4
;
; Pre-loading these modules in package.preload[] means that they're be
; initialized properly through the require() statement.
;---------------------------------------------------------------------*/
lua_getglobal(L,"package");
lua_getfield(L,-1,"preload");
luaL_register(L,NULL,c_preload);
for (size_t i = 0 ; c_luapreload[i].name != NULL ; i++)
{
int rc = luaL_loadbuffer(L,c_luapreload[i].code,*c_luapreload[i].size,c_luapreload[i].name);
if (rc != 0)
{
const char *err;
switch(rc)
{
case LUA_ERRRUN: err = "runtime error"; break;
case LUA_ERRSYNTAX: err = "syntax error"; break;
case LUA_ERRMEM: err = "memory error"; break;
case LUA_ERRERR: err = "generic error"; break;
case LUA_ERRFILE: err = "file error"; break;
default: err = "unknown error"; break;
}
fprintf(stderr,"%s: %s\n",c_luapreload[i].name,err);
exit(EXIT_FAILURE);
}
lua_setfield(L,-2,c_luapreload[i].name);
}
}
/*************************************************************************/
```
Yes, this is the code used in “Project: Wolowizard” (minus the proprietary modules) and is a good example of the module preload feature in Lua. The modules in C are easy to build (the following is from the Makefile):
>
```
obj/spc/process.o : $(LUASPC)/src/process.c \
$(LUA)/lua.h \
$(LUA)/lauxlib.h
$(CC) $(CFLAGS) -I$(LUA) -c -o $@ {body}lt;
```
While the Lua-based modules are a bit more involved:
>
```
obj/spc/unix.o : $(LUASPC)/lua/unix.lua $(BIN2C) $(LUAC)
$(LUAC) -o tmp/unix.out {body}lt;
$(BIN2C) -o tmp/unix.c -t org_conman_unix tmp/unix.out
$(CC) $(CFLAGS) -c -o $@ tmp/unix.c
```
These modules are compiled using luac (which outputs the Lua byte code used by the core Lua VM (Virtual Machine)), then through a program that converts this output into a C file, which is then compiled into an object file that can be linked into the final Kitchen Sink Lua interpreter.
[4] http://luacurl.luaforge.net/
[5] http://www.freelists.org/post/luajit/Trivial-bug-in-bitops-bitc-luaopen-bit,4