Binding Enums To Lua |
|
Here is a very simple (and incomplete) example. The goal was to create a cron-like facility, with a very simple interface:
-- add a task to be run on a given day of the week
cron.add("Monday", task)
-- return an iterator of tasks for a given day of the week
cron.tasks("Wednesday")
The following code does not actually implement these functions; it simply sketches a way of introducing the enums for the days of the week. Most of it is boilerplate code.
/*
The auxiliary function to convert an argument to an enum
This function assumes that the weekday table is the first
upvalue, which in this case it is.
*/
static int get_weekday (lua_State *L, int argno)
{
int weekday;
lua_pushvalue(L, argno);
lua_gettable(L, lua_upvalueindex(1));
weekday = lua_tonumber(L, -1);
lua_pop(L, 1);
if (weekday == 0) {
/* This works because there is no 0 in the weekday
table and lua_tonumber returns 0 for any non-number.
We could have used a lua_isnil() test instead, had 0
been a possible return.
*/
luaL_typerror(L, argno, "weekday");
}
return weekday;
}
/*
These functions actually need to be implemented. All
that is illustrated here is how to get the enum value.
*/
static int cron_add (lua_State *L)
{
int weekday = get_weekday(L, 1);
// ...
}
static int cron_tasks (lua_State *L)
{
int weekday = get_weekday(L, 1);
// ...
}
/* As usual, a luaL_reg of function names and functions */
static const luaL_reg cron_funcs[] = {
{"add", cron_add},
{"tasks", cron_tasks}
{NULL, NULL}
};
/*
The function which creates the library; it must start by
creating the enum table. This function needs to be added
to the list of open functions called when the lua_State is
created (unless the library is going to be loaded dynamically,
of course, but that requires the same function.)
*/
int luaopen_cron (lua_State *L) {
/* make the weekday table */
lua_newtable(L);
lua_pushliteral(L, "Monday"); lua_pushnumber(L, 1); lua_settable(L, -3);
lua_pushliteral(L, "Tuesday"); lua_pushnumber(L, 2); lua_settable(L, -3);
lua_pushliteral(L, "Wednesday"); lua_pushnumber(L, 3); lua_settable(L, -3);
lua_pushliteral(L, "Thursday"); lua_pushnumber(L, 4); lua_settable(L, -3);
lua_pushliteral(L, "Friday"); lua_pushnumber(L, 5); lua_settable(L, -3);
lua_pushliteral(L, "Saturday"); lua_pushnumber(L, 6); lua_settable(L, -3);
lua_pushliteral(L, "Sunday"); lua_pushnumber(L, 7); lua_settable(L, -3);
/* Register the library */
luaL_openlib(L, "cron", cron_funcs, 1);
return 1;
}
The sequence of pushliteral, pushnumber, settable is a big ugly and hard to maintain. Fortunately, we can solve that with a macro.
#define LUA_ENUM(L, name, val) \ lua_pushlstring(L, #name, sizeof(#name)-1); \ lua_pushnumber(L, val); \ lua_settable(L, -3);
Now we can even do this:
lua_newtable(L);
{
int i = 1;
LUA_ENUM(L, Monday, i++);
LUA_ENUM(L, Tuesday, i++);
LUA_ENUM(L, Wednesday, i++);
LUA_ENUM(L, Thursday, i++);
LUA_ENUM(L, Friday, i++);
LUA_ENUM(L, Saturday, i++);
LUA_ENUM(L, Sunday, i++);
}
But we can do even better than that, with a little bit of CPP magic. We could use the same data to create both a C enum and the Lua conversion table.
#define C_ENUM_HELPER(cname, luaname) cname,
#define LUA_ENUM_HELPER(cname, luaname) LUA_ENUM(L, luaname, cname)
#define WEEKDAY \
E(WEEKDAY_MONDAY, Monday) \
E(WEEKDAY_TUESDAY, Tuesday) \
E(WEEKDAY_WEDNESDAY, Wednesday) \
E(WEEKDAY_THURSDAY, Thursday) \
E(WEEKDAY_FRIDAY, Friday) \
E(WEEKDAY_SATURDAY, Saturday) \
E(WEEKDAY_SUNDAY, Sunday)
/* In the c header file: */
#define E C_ENUM_HELPER
enum Weekday {
WEEKDAY
WEEKDAY_LAST /* Avoid the trailing comma problem */
};
#undef E
/* In the Lua file: */
#define E LUA_ENUM_HELPER
lua_newtable(L);
WEEKDAY
#undef E