将递归结构公开为Lua表



由于历史原因,我正在C++中构建一个模拟lua表的结构。

typedef union Value Value;
typedef struct LuaTable LuaTable;
typedef struct TableValue TableValue;
enum ValueType {
INT = 0,
BOOL = 1,
FLOAT = 2,
STRING = 3,
TABLE = 4,
};
struct LuaTable {
std::vector<TableValue> array;
std::map<std::string, TableValue> hashmap;
};
union Value {
int c_int;
bool c_bool;
double c_float;
std::string c_str;
LuaTable table;
};
struct TableValue {
ValueType type;
Value val;
};

我可以简化这一点,使表listmap

我想通过LuaJIT FFI访问这个结构,并使用一些高级语法:

x = c_tbl.a.b.c

是的代理

LuaTable l_tbl {...};
...
l_tbl['a']['b']['c']

在我开始考虑懒惰评估(在一些元表中建立前缀(之前,我通常想知道我是否以正确的方式考虑这个问题。

最简单的方法是通过解析LuaTable结构来初始化c_tbl。这就是我陷入困境的地方。有没有一种简单的方法来解析这样的递归结构?我愿意重新设计它来减轻解析/遍历的困难。

如果您可以使用Lua C API而不是FFI:,那么类似的方法也会起作用

#include <string>
#include <map>
#include <vector>
#include <lua5.1/lua.hpp>
typedef struct LuaTable LuaTable;
typedef struct TableValue TableValue;
struct LuaTable {
std::vector<TableValue> array;
std::map<std::string, TableValue> hashmap;
};
struct TableValue {
enum {
INT = 0,
BOOL = 1,
FLOAT = 2,
STRING = 3,
TABLE = 4,
} type;
union {
int c_int;
bool c_bool;
double c_float;
std::string c_str;
LuaTable table;
};
TableValue(int i) : type(INT), c_int(i) {}
TableValue(bool b) : type(BOOL), c_bool(b) {}
TableValue(double d) : type(FLOAT), c_float(d) {}
TableValue(const char *c) : type(STRING), c_str(c) {}
TableValue(const std::string &s) : type(STRING), c_str(s) {}
TableValue(std::string &&s) : type(STRING), c_str(s) {}
TableValue(const LuaTable &t) : type(TABLE), table(t) {}
TableValue(LuaTable &&t) : type(TABLE), table(t) {}
TableValue(const TableValue &other) : type(other.type) {
switch(type) {
case INT:
c_int = other.c_int;
break;
case BOOL:
c_bool = other.c_bool;
break;
case FLOAT:
c_float = other.c_float;
break;
case STRING:
new (&c_str) std::string(other.c_str);
break;
case TABLE:
new (&table) LuaTable(other.table);
}
}
// TODO write assignment operators and move constructor
~TableValue() {
switch(type) {
case STRING:
{
using std::string;
c_str.~string();
}
break;
case TABLE:
table.~LuaTable();
}
}
};
static void q64936405_push(lua_State *L, const TableValue *v) {
if(!v) {
lua_pushnil(L);
return;
}
switch(v->type) {
case TableValue::INT:
lua_pushinteger(L, v->c_int);
break;
case TableValue::BOOL:
lua_pushboolean(L, v->c_bool);
break;
case TableValue::FLOAT:
lua_pushnumber(L, v->c_float);
break;
case TableValue::STRING:
lua_pushlstring(L, v->c_str.c_str(), v->c_str.length());
break;
case TableValue::TABLE:
// TODO consider making a weak table somewhere to hold these, so retrieving the same subtable multiple times doesn't make duplicate userdata
// and also make them compare equal like they would in a real table
const LuaTable **t = static_cast<const LuaTable **>(lua_newuserdata(L, sizeof *t));
*t = &v->table;
lua_getfield(L, LUA_REGISTRYINDEX, "q64936405");
lua_setmetatable(L, -2);
break;
}
}
static int q64936405_index(lua_State *L) {
const LuaTable *t = *static_cast<const LuaTable **>(luaL_checkudata(L, 1, "q64936405"));
if(lua_isnumber(L, 2)) {
lua_Integer k = lua_tointeger(L, 2);
if(k >= 1 && k <= t->array.size()) {
q64936405_push(L, &t->array[k - 1]);
} else {
lua_pushnil(L);
}
} else {
size_t len;
const char *rawk = luaL_checklstring(L, 2, &len);
const TableValue *v;
{ // this block makes sure a Lua error won't cause UB by skipping the iterator's destruction
auto it = t->hashmap.find(std::string{rawk, len});
if(it == t->hashmap.end()) {
v = nullptr;
} else {
v = &it->second;
}
}
q64936405_push(L, v);
}
return 1;
}
static int q64936405_len(lua_State *L) {
const LuaTable *t = *static_cast<const LuaTable **>(luaL_checkudata(L, 1, "q64936405"));
lua_pushinteger(L, t->array.size());
return 1;
}
// it's important that once this is passed to Lua, it's immutable until the lua_State is closed
// otherwise Bad Things will happen
static const LuaTable the_struct{
{
42,
true,
0.5,
"foo",
LuaTable{
{
"nested"
}, {
{"foo", "bar"}
}
}
}, {
{"one", 1},
{"two", 2}
}
};
extern "C"
int luaopen_q64936405(lua_State *L) {
const LuaTable **v = static_cast<const LuaTable **>(lua_newuserdata(L, sizeof *v));
*v = &the_struct;
luaL_newmetatable(L, "q64936405");
lua_pushcfunction(L, q64936405_index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, q64936405_len);
lua_setfield(L, -2, "__len");
lua_setmetatable(L, -2);
// TODO write __pairs and __ipairs
return 1;
}

我把你的ValueTypeValue卷成了TableValue,但在其他方面保持了你想要的结构。

用这个测试:

local q64936405 = require('q64936405')
print(q64936405[1])
print(q64936405[2])
print(q64936405[3])
print(q64936405[4])
print(q64936405[5][1])
print(q64936405[5].foo)
print(q64936405.one)
print(q64936405.two)
print(#q64936405)

结果:

42
true
0.5
foo
nested
bar
1
2
5

最新更新