Files
lunch-games/source/luad/table.d
2025-04-29 01:59:15 +02:00

350 lines
7.4 KiB
D

module luad.table;
import luad.c.all;
import luad.base;
import luad.stack;
import luad.conversions.structs;
/// Represents a Lua table.
struct LuaTable
{
/// LuaTable sub-types $(DPREF base, LuaObject) through this reference.
LuaObject object;
alias object this;
package this(lua_State* L, int idx)
{
LuaObject.checkType(L, idx, LUA_TTABLE, "LuaTable");
object = LuaObject(L, idx);
}
/**
* Lookup a value in this table or in a sub-table of this table.
* Params:
* T = type of value
* args = list of keys, where all keys but the last one should result in a table
* Returns:
* $(D t[k]) where $(D t) is the table for the second-to-last parameter, and $(D k) is the last parameter
*
* Examples:
* ----------------------
auto execute = lua.get!LuaFunction("os", "execute");
execute(`echo hello, world!`);
* ----------------------
*/
T get(T, U...)(U args) @trusted
{
this.push();
foreach(key; args)
{
pushValue(this.state, key);
lua_gettable(this.state, -2);
}
auto ret = getValue!T(this.state, -1);
lua_pop(this.state, args.length + 1);
return ret;
}
/**
* Read a string value in this table without making a copy of the string.
* The read string is passed to $(D dg), and should not be escaped.
* If the value for $(D key) is not a string, $(D dg) is not called.
* Params:
* key = lookup _key
* dg = delegate to receive string
* Returns:
* $(D true) if the value for $(D key) was a string and passed to $(D dg), $(D false) otherwise
* Examples:
--------------------
t[2] = "two";
t.readString(2, str => assert(str == "two"));
--------------------
*/
bool readString(T)(T key, scope void delegate(in char[] str) dg) @trusted
{
this.push();
scope(exit) lua_pop(this.state, 1);
pushValue(this.state, key);
lua_gettable(this.state, -2);
scope(exit) lua_pop(this.state, 1);
if(lua_isstring(this.state, -1) == 0)
return false;
size_t len;
const(char)* cstr = lua_tolstring(this.state, -1, &len);
dg(cstr[0 .. len]);
return true;
}
/**
* Same as calling $(D get!LuaObject) with the same arguments.
* Examples:
* ---------------------
auto luapath = lua["package", "path"];
writefln("LUA_PATH:\n%s", luapath);
* ---------------------
* See_Also:
* $(MREF LuaTable.get)
*/
LuaObject opIndex(T...)(T args)
{
return get!LuaObject(args);
}
/**
* Set a key-value pair in this table.
* Params:
* key = key to _set
* value = value for $(D key)
*/
void set(T, U)(T key, U value) @trusted
{
this.push();
scope(success) lua_pop(this.state, 1);
pushValue(this.state, key);
pushValue(this.state, value);
lua_settable(this.state, -3);
}
/**
* Set a key-value pair this table or in a sub-table of this table.
* Params:
* value = value to set
* args = list of keys, where all keys but the last one should result in a table
* Returns:
* $(D t[k] = value), where $(D t) is the table for the second-to-last parameter in args,
* and $(D k) is the last parameter in args
*
* Examples:
* ----------------------
lua["string", "empty"] = (in char[] s){ return s.length == 0; };
lua.doString(`assert(string.empty(""))`);
* ----------------------
*/
void opIndexAssign(T, U...)(T value, U args) @trusted
{
this.push();
scope(success) lua_pop(this.state, 1);
foreach(i, arg; args)
{
static if(i != args.length - 1)
{
pushValue(this.state, arg);
lua_gettable(this.state, -2);
}
}
pushValue(this.state, args[$-1]);
pushValue(this.state, value);
lua_settable(this.state, -3);
lua_pop(this.state, args.length - 1);
}
/**
* Create struct of type $(D T) and fill its members with fields from this table.
*
* Struct fields that are not present in this table are left at their default value.
*
* Params:
* T = any struct type
*
* Returns:
* Newly created struct
*/
T toStruct(T)() @trusted if (is(T == struct))
{
push();
return popValue!T(this.state);
}
/**
* Fill a struct's members with fields from this table.
* Params:
* s = struct to fill
*/
void copyTo(T)(ref T s) @trusted if (is(T == struct))
{
push();
fillStruct(this.state, -1, s);
lua_pop(L, 1);
}
/**
* Set the metatable for this table.
* Params:
* meta = new metatable
*/
void setMetaTable(ref LuaTable meta) @trusted
in{ assert(this.state == meta.state); }
body
{
this.push();
meta.push();
lua_setmetatable(this.state, -2);
lua_pop(this.state, 1);
}
/**
* Get the metatable for this table.
* Returns:
* A reference to the metatable for this table. The reference is nil if this table has no metatable.
*/
LuaTable getMetaTable() @trusted
{
this.push();
scope(success) lua_pop(this.state, 1);
return lua_getmetatable(this.state, -1) == 0? LuaTable() : popValue!LuaTable(this.state);
}
/**
* Get the array length of the table.
*/
size_t length() @trusted
{
this.push();
size_t len = lua_objlen(this.state, -1);
lua_pop(this.state, 1);
return len;
}
/**
* Iterate over the values in this table.
*/
int opApply(T)(int delegate(ref T value) dg) @trusted
{
this.push();
lua_pushnil(this.state);
while(lua_next(this.state, -2) != 0)
{
auto value = popValue!T(this.state);
int result = dg(value);
if(result != 0)
{
lua_pop(this.state, 2);
return result;
}
}
lua_pop(this.state, 1);
return 0;
}
/**
* Iterate over the key-value pairs in this table.
*/
int opApply(T, U)(int delegate(ref U key, ref T value) dg) @trusted
{
this.push();
lua_pushnil(this.state);
while(lua_next(this.state, -2) != 0)
{
auto value = popValue!T(this.state);
auto key = getValue!U(this.state, -1);
int result = dg(key, value);
if(result != 0)
{
lua_pop(this.state, 2);
return result;
}
}
lua_pop(this.state, 1);
return 0;
}
}
unittest
{
lua_State* L = luaL_newstate();
scope(success)
{
assert(lua_gettop(L) == 0);
lua_close(L);
}
lua_newtable(L);
auto t = popValue!LuaTable(L);
assert(t.type == LuaType.Table);
t.set("foo", "bar");
assert(t.get!string("foo") == "bar");
t.set("foo", nil);
assert(t.get!LuaObject("foo").isNil);
t.set("foo", ["outer": ["inner": "hi!"]]);
auto s = t.get!(string)("foo", "outer", "inner");
assert(s == "hi!");
auto o = t["foo", "outer"];
assert(o.type == LuaType.Table);
t["foo", "outer", "inner"] = "hello!";
auto s2 = t.get!(string)("foo", "outer", "inner");
assert(s2 == "hello!");
// length
t.set("array", ["one", "two"]);
auto a = t.get!LuaTable("array");
assert(a.length == 2);
// readString
t[2] = "two";
bool success = t.readString(2, (in char[] str) {
assert(str == "two");
});
assert(success);
t[2] = true;
success = t.readString(2, (in char[] str) { assert(false); });
assert(!success);
// metatable
pushValue(L, ["__index": (LuaObject self, string key){
return key;
}]);
auto meta = popValue!LuaTable(L);
lua_newtable(L);
auto t2 = popValue!LuaTable(L);
t2.setMetaTable(meta);
auto test = t2.get!string("foobar");
assert(test == "foobar");
assert(t2.getMetaTable() == meta);
// opApply
auto input = [1, 2, 3];
pushValue(L, input);
auto applyTest = popValue!LuaTable(L);
int i = 0;
foreach(int v; applyTest)
{
assert(input[i++] == v);
}
auto inputWithKeys = ["one": 1, "two": 2, "three": 3];
pushValue(L, inputWithKeys);
auto applyTestKeys = popValue!LuaTable(L);
foreach(string key, int value; applyTestKeys)
{
assert(inputWithKeys[key] == value);
}
}