patterncppMinor
C++ API for interfacing with Lua
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
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
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_tohttps://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:
To this:
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
I mean like this would be more readable:
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
It makes me wonder about the programmers intent? Perhaps, instead, you should define a:
Which you can then comment out the
Later on.
These two long lines in this constructor:
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:
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,
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_DEBUGWhich 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_DEBUGContext
StackExchange Code Review Q#2958, answer score: 8
Revisions (0)
No revisions yet.