HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C++ API for interfacing with Lua

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
interfacingwithforapilua

Problem

I wanted a good way to move objects back and forth between Lua and C++, and I didn't want to use anything like LuaBind or the other available libraries I could find, so I instead wrote this. It's designed to be similar to the normal Lua API, which has functions like lua_tostring or lua_tonumber, but I wanted to make it 'just work' with whatever type I fed it. Basically for any function that had a variant for different types, I added a templated version of it. e.g. luaW_to

https://bitbucket.org/alexames/luawrapper

```
// API Summary:
//
// LuaWrapper is a library designed to help bridge the gab between Lua and
// C++. It is designed to be small (a single header file), simple, fast,
// and typesafe. It has no external dependencies, and does not need to be
// precompiled; the header can simply be dropped into a project and used
// immediately. It even supports class inheritance to a certain degree. Objects
// can be created in either Lua or C++, and passed back and forth.
//
// In Lua, the objects are userdata, but through tricky use of metatables, they
// can be treated almost identically to tables.
//
// The main functions of interest are the following:
// luaW_is
// luaW_to
// luaW_check
// luaW_push
// luaW_register
// luaW_hold
// luaW_release
// luaW_clean
//
// These functions allow you to manipulate arbitrary classes just like you
// would the primitive types (e.g. numbers or strings). When all references
// to a userdata removed, the userdata will be deleted. In some cases, this
// may not be what you want, such as cases where an object is created in Lua,
// then passed to C++ code which owns it from then on. In these cases, you can
// call luaW_release, which releases LuaWrapper's hold on the userdata. This
// prevents it from being deallocated when all references disappear. When this
// is called, you are now responsible for calling luaW_clean manually when you
// are done with the object. Conversely, if an object is created in C++, but
// wo

Solution

First thing I would do is standardize indents, from this:

void luaW_printstack(lua_State* L)
{
    int stack = lua_gettop(L);
    for (int i = 1; i <= stack; i++)
    {
        std::cout << std::dec << i << ": " << lua_typename(L, lua_type(L, i));
        switch(lua_type(L, i))
        {
        case LUA_TBOOLEAN: std::cout << " " << lua_toboolean(L, i); break;
        case LUA_TSTRING: std::cout << " " << lua_tostring(L, i); break;
        case LUA_TNUMBER: std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; break;
        default: std::cout << " " << std::hex << lua_topointer(L, i); break;
        }
        std::cout << std::endl;
    }
}


To this:

void luaW_printstack(lua_State* L)
{
    int stack = lua_gettop(L);
    for (int i = 1; i <= stack; i++)
    {
        std::cout << std::dec << i << ": " << lua_typename(L, lua_type(L, i));
        switch(lua_type(L, i))
        {
            case LUA_TBOOLEAN: std::cout << " " << lua_toboolean(L, i); break;
            case LUA_TSTRING: std::cout << " " << lua_tostring(L, i); break;
            case LUA_TNUMBER: std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; break;
            default: std::cout << " " << std::hex << lua_topointer(L, i); break;
        }
        std::cout << std::endl;
    }
}


In my opinion this makes it much easier to follow. This allows the reader to quickly skim and find the hierarchy of the block they are in much quicker. Generally, when working in languages like the C/C99/C++/C# languages, Python, PHP, Perl, Visual Basic, etc., we assume each line is a statement. (Or no more than a couple.) When many statements are combined on a single line, we start to make mistakes and our assumptions can lead us astray.

Similarly, though less critical, the case statements are generally most readable when broken across lines, though that is up to you.
I mean like this would be more readable:

switch(lua_type(L, i))
{
    case LUA_TBOOLEAN:
        std::cout << " " << lua_toboolean(L, i);
        break;
    case LUA_TSTRING: 
        std::cout << " " << lua_tostring(L, i); 
        break;
    case LUA_TNUMBER: 
        std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; 
        break;
    default:
        std::cout << " " << std::hex << lua_topointer(L, i);
        break;
}


It allows the reader (and likely programmer) to easily distinguish block-from-block, and thus, each section of code from each other section of code.

Pre-processor if statements are very powerful, so when I see something like this:

#if 0
// For Debugging
// Prints the current Lua stack, including the values for some types
template 


It makes me wonder about the programmers intent? Perhaps, instead, you should define a:

#define LUA_DEBUG

#if LUA_DEBUG


Which you can then comment out the #define line to change the effect of it. You could reuse this later, of course, to make things much easier to deal with.
Later on.

These two long lines in this constructor:

struct LuaWrapperOptions
{
    LuaWrapperOptions(
        const luaL_reg* table = NULL, const luaL_reg* metatable = NULL, const char** extends = NULL, bool disablenew = false, T* (*allocator)() = luaW_defaultallocator, void (*deallocator)(T*) = luaW_defaultdeallocator)
        : table(table), metatable(metatable), extends(extends), disablenew(disablenew), allocator(allocator), deallocator(deallocator) { }

    const luaL_reg* table;
    const luaL_reg* metatable;
    const char** extends;
    bool disablenew;
    T* (*allocator)();
    void (*deallocator)(T*);
};


Are very hard to read, though not on purpose. All those inline default values tend to make it hard to read. (You can't even tell how many there are without going on some deep comma-searching.) Cleaning that up makes it far clearer to follow in the future. Even a small change such as the following helps:

struct LuaWrapperOptions
{
    LuaWrapperOptions(
        const luaL_reg* table = NULL,
        const luaL_reg* metatable = NULL,
        const char** extends = NULL,
        bool disablenew = false,
        T* (*allocator)() = luaW_defaultallocator,
        void (*deallocator)(T*) = luaW_defaultdeallocator)
        : table(table), metatable(metatable), extends(extends), disablenew(disablenew), allocator(allocator), deallocator(deallocator) { }

    const luaL_reg* table;
    const luaL_reg* metatable;
    const char** extends;
    bool disablenew;
    T* (*allocator)();
    void (*deallocator)(T*);
};


This allows the reader to easily tell that there are just a few parameters there. Though, it could be improved much more, by breaking things more verbosely, as so:

```
struct LuaWrapperOptions
{
LuaWrapperOptions(
const luaL_reg* table = NULL,
const luaL_reg* metatable = NULL,

Code Snippets

void luaW_printstack(lua_State* L)
{
    int stack = lua_gettop(L);
    for (int i = 1; i <= stack; i++)
    {
        std::cout << std::dec << i << ": " << lua_typename(L, lua_type(L, i));
        switch(lua_type(L, i))
        {
        case LUA_TBOOLEAN: std::cout << " " << lua_toboolean(L, i); break;
        case LUA_TSTRING: std::cout << " " << lua_tostring(L, i); break;
        case LUA_TNUMBER: std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; break;
        default: std::cout << " " << std::hex << lua_topointer(L, i); break;
        }
        std::cout << std::endl;
    }
}
void luaW_printstack(lua_State* L)
{
    int stack = lua_gettop(L);
    for (int i = 1; i <= stack; i++)
    {
        std::cout << std::dec << i << ": " << lua_typename(L, lua_type(L, i));
        switch(lua_type(L, i))
        {
            case LUA_TBOOLEAN: std::cout << " " << lua_toboolean(L, i); break;
            case LUA_TSTRING: std::cout << " " << lua_tostring(L, i); break;
            case LUA_TNUMBER: std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; break;
            default: std::cout << " " << std::hex << lua_topointer(L, i); break;
        }
        std::cout << std::endl;
    }
}
switch(lua_type(L, i))
{
    case LUA_TBOOLEAN:
        std::cout << " " << lua_toboolean(L, i);
        break;
    case LUA_TSTRING: 
        std::cout << " " << lua_tostring(L, i); 
        break;
    case LUA_TNUMBER: 
        std::cout << " " << std::dec << (uintptr_t)lua_tointeger(L, i) << " (0x" << std::hex << lua_tointeger(L, i) << ")"; 
        break;
    default:
        std::cout << " " << std::hex << lua_topointer(L, i);
        break;
}
#if 0
// For Debugging
// Prints the current Lua stack, including the values for some types
template <typename T>
#define LUA_DEBUG

#if LUA_DEBUG

Context

StackExchange Code Review Q#2958, answer score: 8

Revisions (0)

No revisions yet.