c++ - 将递归结构公开为 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;
};
我可以将其简化为表格是a或list
a map
。
我想通过 LuaJIT FFI 使用一些高级语法访问这个结构:
x = c_tbl.a.b.c
是代表
LuaTable l_tbl {...};
...
l_tbl['a']['b']['c']
在我开始考虑惰性评估(在某些元表中建立前缀)之前,我通常想知道我是否以正确的方式考虑这个问题。
最简单的方法是c_tbl
通过解析LuaTable
结构进行初始化。这就是我卡住的地方。有没有一种直接的方法来解析这样的递归结构?我愿意重新设计它以减轻解析/遍历的困难。
解决方案
如果您可以使用 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;
}
我把你的卷ValueType
进去Value
了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
推荐阅读
- php - 仅当是分组产品的一部分时才从循环中隐藏单个产品
- tensorflow - 错误:tensorflow 1.13.0rc2 要求 tensorboard<1.13.0,>=1.12.0,但您将拥有不兼容的 tensorboard 1.14.0
- javascript - addEvenetListener 不是函数
- python - Ray 本地集群 web-ui 显示 0 个工作人员
- node.js - MERN 堆栈电子邮件确认
- android - Firestore 从子集合中获取所有文档
- python - 列表和元组/列表之间的比较
- javascript - 使用 JS 和 SCSS 的导航动画,你能告诉我哪里出错了吗?
- flask - jinja2.exceptions.UndefinedError: 'flask.ctx._AppCtxGlobals object' 没有属性 'words'
- android - com.google.android.support:wearable:2.4.0 和 AndroidX 迁移