I’m pretty sure the first time I saw DRY – “Don’t Repeat Yourself” – used as an acronym, I was working with Rails. As far as acronyms go, I like it. And, while it’s certainly not the only aspect of good code design worth having a shorthand for, it does serve as a useful reminder from time to time. ( Not to mention, lede for this post. )
When you find yourself typing the same code – or reenacting the same pattern of code – over and over again, DRY asks: how can I automate this? How can I abstract away the details? How can I program my programming?
Enter here the dark side of Lua, because implementing C++ interfaces for Lua code is a lot more RY than D. Declare your class and its member methods. Yes, in both your .h and .cpp. Then, declare static function versions of those methods, again probably in both your .h and .cpp. Now, use those functions to map the Lua calls to the methods ( isn’t this the only part we really care about? ). Then, implement a registration function containing pairs of string names for your functions, and oh yeah, those same functions again. Okay, not the end of the world, but surely we can do better….
Simplifying Lua Registration Link to heading
What follows is one small way to help implement Lua functions. If you’ve been working with Lua for a while, you probably already have your code helper of choice – maybe even a framework like tolua, luabind, or perhaps something home grown. Still, maybe this will spur some ideas for you, or maybe inspire you to share the tricks that you personally find useful.
For Lua, ultimately, we’re just trying to bind a bunch of “stuff” to a table of some sort:
luaL_Reg regTable [] = {
{ "get_stuff", _get_stuff },
{ "set_stuff", _set_stuff },
{ "do_stuff ", _do_stuff },
{ NULL, NULL }
};
luaL_register( L, name, regTable );
So, let’s focus our effort there.
First off, it’d be nice to get the strings defined at the same time as the functions. That indicates we need some macro magic, but in this case it’s worth it. You probably noticed the accidental space after “do_stuff” on the third line, but… stuff… like that is easy to miss when embedded in lots of other code.
Obviously, the simplest helper would be a single macro:
#define LUA_REG( x ) { #x, _##x }
luaL_Reg regTable [] = {
LUA_REG( get_stuff ),
LUA_REG( set_stuff ),
LUA_REG( do_stuff ),
{ 0 }
};
luaL_register( L, name, regTable );
And, given c (and c++) structure initialization rules, we don’t need that second NULL anyway. But, ideally, we could combine that table with the actual function declarations, because elsewhere in the file we still have:
// hey, it's that same name again.
int MaybeAClass::_do_stuff( lua_State* L ) {
// do some stuff for Lua
}
Unfortunately, because luaL_register() relies on the NULL terminated array, there’s not going to be a good way to inject function definitions inside the table itself ( okay, maybe with C++11 lambda ), but then… all luaL functions are just helpers. If we were willing to re-implement it, we could use any kind of magic we want.
One technique, for instance, would be to use C++ static initialization to help build a list of functions as we declare them. Later, at registration time, we could the walk that list to bind all the functions generically. A program, in essence, to help us write a simpler program.
Conceptually, what we need to support something like that is a class to represent each function. Each instance of that class will be a node in a list, and we will use a separate class or object to track the list as a whole. The class will store a pointer to the lua function in question, a pointer to that function’s name, and the previous node in the list.
Lua helps us out a lot with all this, because all of its callbacks use exactly the same signature: int function( lua_State* ). This means we won’t need any template magic, or other special case handling. We just need the function pointer.
// a lua function registration node in a linked list:
struct LuaReg {
LuaReg(const char *name, lua_CFunction func, LuaReg**curr)
: name(name), func(func)
, prev( *curr ) // link back to whatever's already in the list
{
*curr= this; // update the list head to point to us
}
private:
const char * name;
lua_CFunction func;
LuaReg *prev;
};
// manually creating a node might look like;
static LuaReg * lr_list= NULL;
static LuaReg lr_do_stuff( "do_stuff", _do_stuff, &lr_list );
At first glance, that long manual declaration might not look like an improvement, but it means we can move the declaration out of that NULL-terminated list and close to the function. It also means we can register all functions, in every case, generically. All we have to do is walk in reverse order from the last function declared to the first:
// mimic luaL_register(L, NULL, list)
int LuaReg::Register( lua_State* L, int nup )
{
for (LuaReg *l= this; l; l=l->prev) {
for (int i=0; i<nup; i++) { /* copy upvalues to the top */
lua_pushvalue(L, -nup);
}
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, -(nup+2), l->name);
}
lua_pop(L, nup); /* remove upvalues */
return 0;
}
To avoid arbitrary policy regarding what kind of table these functions are getting pushed into, and what global name the table should be registered with ( if any ) – we’ll only specify that we need some table ( any table ) to be on the top of the stack. The caller can setup that table as needed, then run the function registration process. ( This is actually the same behavior as calling luaL_register with a NULL name. )
That feels promising enough, but let’s see those macros and an example already! Okay, but one quick thing to keep in mind: sometimes it will prove useful to have multiple groups of functions – whether that be multiple classes, or maybe a set of class functions and a set of instance functions – in the same file. Whatever macros we do setup should, therefore, allow for multiple lists. Meaning, we’ll not only specify a function name, we’ll also specify a group name.
The macro I like is this:
#define LUA_FUNCTION( group, func ) \
group##_##func( lua_State* ); \
static LuaReg _lr_##group##_##func( #func, group##_##func, &lr_##group ); \
int group##_##func
because it allows for function declarations like this:
int LUA_FUNCTION( example, hello_world )( lua_State *L ) {
lua_pushstring( L, "Hello World!" );
return 1;
}
Wait that looks pretty cool, but I thought you said: “DRY”. Why are you specifying the int, and the lua_State again, and again? Can’t we chop all that out?
This is somewhat a matter of taste, but it’s an example of why I think DRY shouldn’t be followed to the ends of the earth: it’s not the only metric for good coding. Readability is usually more important, ease of use, debug-ability, the list goes on.
In general, when you are relying on macro-magic ( and maybe, especially when you are ), you want your code to look as normal as possible. The macro eliminated the strings and the table but otherwise, it still looks pretty much like a function declaration. That feels like a good balance to me.
As another matter of taste, I didn’t make the functions static members of an associated class, but on that I remain agnostic. The macro could be tweaked into making the group the class to help support that pattern.
The last thing to determine, really, is just how to declare the group. At the bare minimum we need that linked list pointer, but a helper function to register the group might be nice as well…
#define LUA_FUNCTIONS( group ) \
static LuaReg * lr_##group=0; \
int Register_##group(lua_State *L, int nup=0) { \
return lr_##group->Register( L, nup ); \
}
( Complete example the end of the post )
Note: There is one sneaky trick here that probably isn’t fit for production code. If no functions have been registered, the group will be NULL, but Register_group() proceeds blindly on. While you might expect a crash in that case… the code, in fact, doesn’t mind one bit. Since the group’s Register() isn’t virtual, it’s technically fine to call it with a NULL group so long as we test the “this” function before we use it. The linked list for-loop traversal does so implicitly, so all is (technically) well. For production code, a static function would be better. ( Just don’t forget to handle popping the nup values when the list is NULL. )
// .................
// Lua Reg: A simple DRY facility for registering static Lua functions.
// In your .cpp:
// declare a group of functions:
LUA_FUNCTIONS( example );
// define one or more functions in that group:
int LUA_FUNCTION( example, hello_world )( lua_State *L ) {
lua_pushstring( L, "Hello World!" );
return 1;
}
// register all your defined functions with a table on the stack
// w/o re-specifying the names and references to those functions:
Register_example(L);
// .................
#define LUA_FUNCTIONS( group ) \
static LuaReg * lr_##group=0; \
int Register_##group(lua_State *L, int nup=0) { \
return lr_##group->Register( L, nup ); \
}
#define LUA_FUNCTION( group, func ) \
group##_##func( lua_State * ); \
static LuaReg _lr_##group##_##func( #func, group##_##func, &lr_##group ); \
int group##_##func
struct LuaReg {
LuaReg(const char *name, lua_CFunction func, LuaReg**curr)
: name(name), func(func)
, prev( *curr )
{
*curr= this;
}
int Register( lua_State* L, int nup );
private:
const char * name;
lua_CFunction func;
LuaReg *prev;
};
// mimic luaL_register(L, NULL, list)
int LuaReg::Register( lua_State* L, int nup )
{
for (LuaReg *l= this; l; l=l->prev) {
for (int i=0; i<nup; i++) { /* copy upvalues to the top */
lua_pushvalue(L, -nup);
}
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, -(nup+2), l->name);
}
lua_pop(L, nup); /* remove upvalues */
return 0;
}