diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index d223e046..09c81a35 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -459,6 +459,10 @@ int LuaCoreStack::rawlen(LuaSlot obj) const { return lua_rawlen(L_, obj.index()); } +int LuaCoreStack::nkeys(LuaSlot obj) const { + return lua_nkeys(L_, obj.index()); +} + int LuaCoreStack::gettabletype(LuaSlot tab) const { uint16_t bits = lua_getflagbits(L_, tab.index()); return LUA_TT_GENERAL + (bits & 0x000F); diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index df11b2e3..d00a8f82 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -410,6 +410,8 @@ public: int rawlen(LuaSlot val) const; + int nkeys(LuaSlot tab) const; + int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; // Return true if the classname is legal. diff --git a/luprex/cpp/core/table.cpp b/luprex/cpp/core/table.cpp index 53040aaa..d6d7340f 100644 --- a/luprex/cpp/core/table.cpp +++ b/luprex/cpp/core/table.cpp @@ -4,6 +4,24 @@ #include "table.hpp" #include "source.hpp" +// A quick check to see if a table appears to be a vector. +// Does not thoroughly verify the vector. Returns the size +// of the vector. +static int check_vector_quick(LuaCoreStack &LS, LuaSlot vector, LuaSlot tmp) { + LS.checktable(vector, "vector"); + int nkeys = LS.nkeys(vector); + if (nkeys > 0) { + LS.rawget(tmp, vector, nkeys); + if (LS.isnil(tmp)) { + luaL_error(LS.state(), "Not a valid vector"); + } + LS.rawget(tmp, vector, nkeys + 1); + if (!LS.isnil(tmp)) { + luaL_error(LS.state(), "Not a valid vector"); + } + } + return nkeys; +} bool table_equal(LuaCoreStack &LS, LuaSlot t1, LuaSlot t2) { lua_State *L = LS.state(); @@ -30,7 +48,11 @@ bool table_equal(LuaCoreStack &LS, LuaSlot t1, LuaSlot t2) { return true; } -LuaDefine(table_equal, "table1,table2", "return true if two tables contain the same keys and values") { +LuaDefine(table_equal, "table1,table2", + "|Return true if two tables contain the same keys and values." + "|" + "|This works on arbitrary tables. Metatables are ignored." + "|") { LuaArg t1, t2; LuaRet eql; LuaDefStack LS(L, t1, t2, eql); @@ -38,87 +60,179 @@ LuaDefine(table_equal, "table1,table2", "return true if two tables contain the s return LS.result(); } -LuaDefine(table_findremove, "vector,value", "remove all occurrences of value from vector") { - luaL_checktype(L, -2, LUA_TTABLE); - int src = 1; - int dst = 1; - while (true) { - lua_pushinteger(L, src); - lua_rawget(L, -3); - if (lua_rawequal(L, -1, -2)) { - src++; - lua_pop(L, 1); - } else if (lua_isnil(L, -1)) { - lua_pop(L, 1); - int removed = src - dst; - while (src > dst) { - lua_pushinteger(L, dst); - lua_pushnil(L); - lua_rawset(L, -4); - dst++; - } - lua_pop(L, 2); - lua_pushinteger(L, removed); - return 1; - } else { - if (src > dst) { - lua_pushinteger(L, dst); - lua_insert(L, lua_gettop(L) - 1); - lua_rawset(L, -4); - } else { - lua_pop(L, 1); - } - src++; - dst++; +static int check_isvector(lua_State *L) { + LuaArg table; + LuaRet result; + LuaVar tmp; + LuaDefStack LS(L, table, result, tmp); + if (!LS.istable(table)) { + LS.set(result, false); + return LS.result(); + } + int nkeys = lua_nkeys(L, table.index()); + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, table, i); + if (LS.isnil(tmp)) { + LS.set(result, false); + return LS.result(); } } + LS.set(result, true); + return LS.result(); } - -LuaDefine(table_push, "vector,value", "push a value onto the end of a vector") { - luaL_checktype(L, -2, LUA_TTABLE); - int len = lua_rawlen(L, -2); - lua_pushinteger(L, len+1); - lua_pushvalue(L, -2); - lua_rawset(L, -4); - lua_pop(L, 2); - return 0; +LuaDefine(table_isvector, "table", + "|Return true if the table is a valid vector." + "|" + "|A vector is a table that has keys starting with 1, and no gaps." + "|The empty table also counts as a valid vector." + "|" + "|This function is identical to vector.isvector" + "|") { + return check_isvector(L); } -LuaDefine(table_find, "vector,value", "find the first occurrence of value in vector") { - luaL_checktype(L, -2, LUA_TTABLE); - for (int i = 1; ; i++) { - lua_pushinteger(L, i); - lua_rawget(L, -3); - if (lua_rawequal(L, -1, -2)) { - lua_pop(L, 3); - lua_pushinteger(L, i); - return 1; - } else if (lua_isnil(L, -1)) { - lua_pop(L, 3); - lua_pushnil(L); - return 1; - } else { - lua_pop(L, 1); +LuaDefine(vector_isvector, "table", + "|Return true if the table is a valid vector." + "|" + "|A vector is a table that has keys starting with 1, and no gaps." + "|The empty table also counts as a valid vector." + "|" + "|This function is identical to table.isvector" + "|") { + return check_isvector(L); +} + +LuaDefine(vector_removeall, "vector,value", + "|Remove all occurrences of value from vector." + "|" + "|For example, if you remove the number 3 from the vector" + "|{1,2,3,4,5,4,3,2,1} you get {1,2,4,5,4,2,1}." + "|" + "|Returns true if it removed something, false otherwise." + "|") { + LuaArg vector, value; + LuaRet result; + LuaVar tmp; + LuaDefStack LS(L, vector, value, result, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + int dest = 1; + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, vector, i); + if (LS.isnil(tmp)) { + luaL_error(L, "not a valid vector"); + return LS.result(); + } + if (!LS.rawequal(tmp, value)) { + if (dest < i) { + LS.rawset(vector, dest, tmp); + } + dest += 1; } } + LS.set(result, (dest < nkeys)); + while (dest <= nkeys) { + LS.rawset(vector, dest, LuaNil); + dest += 1; + } + return LS.result(); } -LuaDefine(table_empty, "table", "return true if the table has zero keys") { +LuaDefine(vector_push, "vector,value", + "|Push a value onto the end of a vector." + "|" + "|Argument must be a valid vector. Appends the value to" + "|the end of the vector." + "|") { + LuaArg vector, value; + LuaVar tmp; + LuaDefStack LS(L, vector, value, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + LS.rawset(vector, nkeys + 1, value); + return LS.result(); +} + +LuaDefine(vector_pop, "vector,value", + "|Pop a value from the end of a vector." + "|" + "|Argument must be a valid vector. Returns the last value" + "|from the vector. If the vector is empty, returns nil." + "|") { + LuaArg vector; + LuaRet value; + LuaVar tmp; + LuaDefStack LS(L, vector, value, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + if (nkeys == 0) { + LS.set(value, LuaNil); + } else { + LS.rawget(value, vector, nkeys); + LS.rawset(vector, nkeys, LuaNil); + } + return LS.result(); +} + +LuaDefine(vector_find, "vector,value", + "|Find the first occurence of value in vector." + "|" + "|Argument must be a valid vector. Returns the index of the" + "|first occurrence of value in vector. If the value is not" + "|found, returns nil." + "|" + "|Searching for 'nil' in a vector is explicitly disallowed, since" + "|a valid vector cannot contain nil." + "|") { + LuaArg vector, value; + LuaRet index; + LuaVar tmp; + LuaDefStack LS(L, vector, value, index, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + if (LS.isnil(value)) { + luaL_error(L, "cannot search for nil in a vector"); + return 0; + } + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, vector, i); + if (LS.rawequal(tmp, value)) { + LS.set(index, i); + return LS.result(); + } + } + LS.set(index, LuaNil); + return LS.result(); +} + +LuaDefine(table_empty, "table", + "|Return true if the table is empty." + "|" + "|Empty means the table has no key-value pairs stored." + "|Metatables and metavalues are ignored." + "|") { luaL_checktype(L, -1, LUA_TTABLE); int total = lua_nkeys(L, -1); lua_pushboolean(L, (total == 0)?1:0); return 1; } -LuaDefine(table_count, "table", "return the number of keys in table") { +LuaDefine(table_count, "table", + "|Return the number of key-value pairs in the table." + "|" + "|Returns the number of key-value pairs that have been" + "|explicitly stored. Metatables and metavalues don't count." + "|") { luaL_checktype(L, -1, LUA_TTABLE); int total = lua_nkeys(L, -1); lua_pushinteger(L, total); return 1; } -LuaDefine(table_clear, "table,metaflag", "clear all keys, and optionally the metatable") { +LuaDefine(table_clear, "table,metaflag", + "|Clear the table key-value pairs, and optionally the metatable" + "|" + "|This removes all key-value pairs from the table. If the metaflag" + "|is set, it also removes the metatable. If you try to clear the" + "|metatable when the metatable is locked, this will throw an error." + "|") { LuaArg tab, clearmeta; LuaVar metatable, metafield; LuaDefStack LS(L, tab, clearmeta, metatable, metafield); diff --git a/luprex/lua/ut-table.lua b/luprex/lua/ut-table.lua index 6831d98f..9a043c86 100644 --- a/luprex/lua/ut-table.lua +++ b/luprex/lua/ut-table.lua @@ -31,25 +31,46 @@ function unittests.tables() assert(table.equal({a=1,b=2},{a=1,b=2})) assert(not table.equal({a=1,b=3},{a=1,b=2})) - -- check table.push - t = {} - table.push(t, 1) - assert(table.equal(t, {1})) - table.push(t, 2) - assert(table.equal(t, {1,2})) - table.push(t, 3) - assert(table.equal(t, {1,2,3})) - -- check table.findremove - t = {1,2,3,4,5,1,2,3,4,5} - table.findremove(t, 2) - assert(table.equal(t, {1,3,4,5,1,3,4,5})) - table.findremove(t, 5) - assert(table.equal(t, {1,3,4,1,3,4})) - table.findremove(t, 1) - assert(table.equal(t, {3,4,3,4})) end +function unittests.vectors() + assert(true == table.isvector{1,2,3}) + assert(false == table.isvector{1,2,nil,3}) + + -- check vector.removeall + t = {1,2,3,4,5,1,2,3,4,5} + assert(true == vector.removeall(t, 2)) + assert(table.equal(t, {1,3,4,5,1,3,4,5})) + assert(false == vector.removeall(t, 7)) + assert(table.equal(t, {1,3,4,5,1,3,4,5})) + assert(true == vector.removeall(t, 5)) + assert(table.equal(t, {1,3,4,1,3,4})) + assert(true == vector.removeall(t, 1)) + assert(table.equal(t, {3,4,3,4})) + + -- check vector.push + t = {} + vector.push(t, 1) + assert(table.equal(t, {1})) + vector.push(t, 2) + assert(table.equal(t, {1,2})) + vector.push(t, 3) + assert(table.equal(t, {1,2,3})) + + -- check vector.pop + t = {1,2,3} + assert(3 == vector.pop(t)) + assert(table.equal(t, {1,2})) + assert(2 == vector.pop(t)) + assert(table.equal(t, {1})) + assert(1 == vector.pop(t)) + assert(table.equal(t, {})) + assert(nil == vector.pop(t)) + assert(table.equal(t, {})) +end + + function unittests.deque() local d = deque.create() for i=1,7 do