#include "wrap-string.hpp" #include "table.hpp" #include "source.hpp" // A quick check to see if a table appears to be a array. // Does not thoroughly verify the array. Returns the size // of the array. static int check_array_quick(LuaCoreStack &LS, LuaSlot array, LuaSlot tmp) { LS.cktable(array, "array"); int nkeys = LS.nkeys(array); if (nkeys > 0) { LS.rawget(tmp, array, nkeys); if (LS.isnil(tmp)) { luaL_error(LS.state(), "Not a valid array"); } LS.rawget(tmp, array, nkeys + 1); if (!LS.isnil(tmp)) { luaL_error(LS.state(), "Not a valid array"); } } return nkeys; } bool table_equal(LuaCoreStack &LS, LuaSlot t1, LuaSlot t2) { lua_State *L = LS.state(); int top = lua_gettop(L); LS.cktable(t1, "table1"); LS.cktable(t2, "table2"); int nkeys1 = lua_nkeys(L, t1.index()); int nkeys2 = lua_nkeys(L, t2.index()); if (nkeys1 != nkeys2) return false; int total = 0; lua_pushnil(L); while (lua_next(L, t1.index()) != 0) { lua_pushvalue(L, -2); // k v1 k lua_rawget(L, t2.index()); // k v1 v2 if (!lua_rawequal(L, -1, -2)) { lua_settop(L, top); return false; } lua_pop(L, 2); total += 1; } assert(total == nkeys1); lua_settop(L, top); return true; } 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); LS.set(eql, table_equal(LS, t1, t2)); return LS.result(); } LuaDefine(table_isarray, "table", "|Return true if the table is a valid array." "|" "|A array is a table that has keys starting with 1, and no gaps." "|The empty table also counts as a valid array. This function" "|scans the entire array to verify its validity, so it takes O(N)" "|time. See also table.isarrayq" "|" "|The functions array.isarray and table.isarray are identical." "|") { 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_isarrayq, "table", "|Return true if the table is probably a valid array." "|" "|A array is a table that has keys starting with 1, and no gaps." "|The empty table also counts as a valid array. This function" "|does a constant-time heuristic check: it gets the number of keys" "|in the table, NKEYS. Then it verifies that table[NKEYS] is not" "|nil and that table[NKEYS+1] is nil. If these things are both" "|true, the table is very likely a valid array." "|" "|The functions array.isarrayq and table.isarrayq are identical." "|") { 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 = LS.nkeys(table); if (nkeys > 0) { LS.rawget(tmp, table, nkeys); if (LS.isnil(tmp)) { LS.set(result, false); return LS.result(); } LS.rawget(tmp, table, nkeys + 1); if (!LS.isnil(tmp)) { LS.set(result, false); return LS.result(); } } LS.set(result, true); return LS.result(); } LuaDefineAlias(array_isarray, table_isarray); LuaDefine(array_removeall, "array,value", "|Remove all occurrences of value from array." "|" "|For example, if you remove the number 3 from the array" "|{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 array, value; LuaRet result; LuaVar tmp; LuaDefStack LS(L, array, value, result, tmp); int nkeys = check_array_quick(LS, array, tmp); int dest = 1; for (int i = 1; i <= nkeys; i++) { LS.rawget(tmp, array, i); if (LS.isnil(tmp)) { luaL_error(L, "not a valid array"); return LS.result(); } if (!LS.rawequal(tmp, value)) { if (dest < i) { LS.rawset(array, dest, tmp); } dest += 1; } } LS.set(result, (dest < nkeys)); while (dest <= nkeys) { LS.rawset(array, dest, LuaNil); dest += 1; } return LS.result(); } LuaDefine(array_push, "array,value", "|Push a value onto the end of a array." "|" "|Argument must be a valid array. Appends the value to" "|the end of the array." "|") { LuaArg array, value; LuaVar tmp; LuaDefStack LS(L, array, value, tmp); int nkeys = check_array_quick(LS, array, tmp); LS.rawset(array, nkeys + 1, value); return LS.result(); } LuaDefine(array_pop, "array,value", "|Pop a value from the end of a array." "|" "|Argument must be a valid array. Returns the last value" "|from the array. If the array is empty, returns nil." "|") { LuaArg array; LuaRet value; LuaVar tmp; LuaDefStack LS(L, array, value, tmp); int nkeys = check_array_quick(LS, array, tmp); if (nkeys == 0) { LS.set(value, LuaNil); } else { LS.rawget(value, array, nkeys); LS.rawset(array, nkeys, LuaNil); } return LS.result(); } LuaDefine(array_find, "array,value", "|Find the first occurence of value in array." "|" "|Argument must be a valid array. Returns the index of the" "|first occurrence of value in array. If the value is not" "|found, returns nil." "|" "|Searching for 'nil' in a array is explicitly disallowed, since" "|a valid array cannot contain nil." "|") { LuaArg array, value; LuaRet index; LuaVar tmp; LuaDefStack LS(L, array, value, index, tmp); int nkeys = check_array_quick(LS, array, tmp); if (LS.isnil(value)) { luaL_error(L, "cannot search for nil in a array"); return 0; } for (int i = 1; i <= nkeys; i++) { LS.rawget(tmp, array, 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 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 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); LS.cktable(tab, "table"); if (LS.ckboolean(clearmeta)) { LS.getmetatable(metatable, tab); if (LS.istable(metatable)) { LS.rawget(metafield, metatable, "__metatable"); if (!LS.isnil(metafield)) { luaL_error(L, "Cannot clear metatable."); return LS.result(); } } LS.cleartable(tab, true); } else { LS.cleartable(tab, false); } return LS.result(); } LuaDefine(table_getflagbits, "table", "get the table's flag bits (debugging only)") { LuaArg tab; LuaRet bits; LuaDefStack LS(L, tab, bits); uint16_t ubits = lua_getflagbits(L, tab.index()); LS.set(bits, ubits); return LS.result(); } LuaDefine(table_setflagbits, "table,bits", "set the table's flag bits (debugging only)") { LuaArg tab, bits; LuaDefStack LS(L, tab, bits); uint16_t ubits = LS.ckinteger(bits); lua_setflagbits(L, tab.index(), ubits); return LS.result(); } ///////////////////////////////////////////////////////////// // // Deque operators. // ///////////////////////////////////////////////////////////// #define DEQUE_LEFT 1 #define DEQUE_FILL 2 #define DEQUE_MAX 3 #define DEQUE_BASE 4 void deque_checktype(lua_State *L, int slot, int type) { if (lua_type(L, slot) != type) { luaL_error(L, "object is not a valid deque"); } } void deque_get_info(lua_State *L, int deque, int *left, int *fill, int *max) { luaL_checktype(L, deque, LUA_TTABLE); if (left) { lua_rawgeti(L, deque, DEQUE_LEFT); deque_checktype(L, -1, LUA_TNUMBER); *left = lua_tointeger(L, -1); lua_pop(L, 1); } if (fill) { lua_rawgeti(L, deque, DEQUE_FILL); deque_checktype(L, -1, LUA_TNUMBER); *fill = lua_tointeger(L, -1); lua_pop(L, 1); } if (max) { lua_rawgeti(L, deque, DEQUE_MAX); deque_checktype(L, -1, LUA_TNUMBER); *max = lua_tointeger(L, -1); lua_pop(L, 1); } } void deque_put_info(lua_State *L, int deque, int *left, int *fill, int *max) { if (left) { lua_pushinteger(L, *left); lua_rawseti(L, deque, DEQUE_LEFT); } if (fill) { lua_pushinteger(L, *fill); lua_rawseti(L, deque, DEQUE_FILL); } if (max) { lua_pushinteger(L, *max); lua_rawseti(L, deque, DEQUE_MAX); } } int deque_make_room(lua_State *L, int deque, int left, int fill, int max) { if (fill == max) { for (int i = 0; i < left; i++) { lua_rawgeti(L, deque, DEQUE_BASE + i); lua_rawseti(L, deque, DEQUE_BASE + i + max); lua_pushinteger(L, 0); lua_rawseti(L, deque, DEQUE_BASE + i); } for (int i = left; i < max; i++) { lua_pushinteger(L, 0); lua_rawseti(L, deque, DEQUE_BASE + i + max); } max *= 2; lua_pushinteger(L, max); lua_rawseti(L, deque, DEQUE_MAX); } return max; } LuaDefine(deque_create, "", "create a deque") { LuaRet rdeque; LuaVar classobj; LuaDefStack LS(L, rdeque, classobj); const int imax = 4; eng::string err = LS.getclass(classobj, "deque"); if (err != "") { luaL_error(L, "Class deque has been corrupted"); } LS.createtable(rdeque, DEQUE_BASE + imax - 1, 0); LS.rawset(rdeque, DEQUE_LEFT, 0); LS.rawset(rdeque, DEQUE_FILL, 0); LS.rawset(rdeque, DEQUE_MAX, imax); for (int i = 0; i < imax; i++) { LS.rawset(rdeque, DEQUE_BASE + i, 0); } LS.setmetatable(rdeque, classobj); return LS.result(); } LuaDefine(deque_pushl, "deque,value", "push onto the left end of a deque") { LuaArg deque, elt; LuaDefStack LS(L, deque, elt); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); max = deque_make_room(L, deque.index(), left, fill, max); int target = (left - 1) & (max-1); LS.rawset(deque, DEQUE_BASE + target, elt); fill += 1; left = (left - 1) & (max - 1); deque_put_info(L, deque.index(), &left, &fill, NULL); return LS.result(); } LuaDefine(deque_pushr, "deque,value", "push onto the right end of a deque") { LuaArg deque, elt; LuaDefStack LS(L, deque, elt); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); max = deque_make_room(L, deque.index(), left, fill, max); int target = (left + fill) & (max-1); LS.rawset(deque, DEQUE_BASE + target, elt); fill += 1; deque_put_info(L, deque.index(), NULL, &fill, NULL); return LS.result(); } LuaDefine(deque_popl, "deque", "pop the left end of a deque") { LuaArg deque; LuaRet result; LuaDefStack LS(L, deque, result); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); if (fill == 0) { LS.set(result, LuaNil); return LS.result(); } LS.rawget(result, deque, DEQUE_BASE + left); LS.rawset(deque, DEQUE_BASE + left, 0); left = (left + 1) & (max - 1); fill -= 1; deque_put_info(L, deque.index(), &left, &fill, NULL); return LS.result(); } LuaDefine(deque_popr, "deque", "pop the right end of a deque") { LuaArg deque; LuaRet result; LuaDefStack LS(L, deque, result); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); if (fill == 0) { LS.set(result, LuaNil); return LS.result(); } int target = (left + fill - 1) & (max - 1); LS.rawget(result, deque, DEQUE_BASE + target); LS.rawset(deque, DEQUE_BASE + target, 0); fill -= 1; deque_put_info(L, deque.index(), NULL, &fill, NULL); return LS.result(); } LuaDefine(deque_nthl, "deque,n", "return the nth item from the left end of a deque") { LuaArg deque, nn; LuaRet result; LuaDefStack LS(L, deque, nn, result); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); int n = LS.ckint(nn); if ((n < 1) || (n > fill)) { LS.set(result, LuaNil); return LS.result(); } int target = (left + n - 1) & (max - 1); LS.rawget(result, deque, DEQUE_BASE + target); return LS.result(); } LuaDefine(deque_nthr, "deque,n", "return the nth item from the right end of a deque") { LuaArg deque, nn; LuaRet result; LuaDefStack LS(L, deque, nn, result); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); int n = LS.ckint(nn); if ((n < 1) || (n > fill)) { LS.set(result, LuaNil); return LS.result(); } int target = (left + fill - n) & (max - 1); LS.rawget(result, deque, DEQUE_BASE + target); return LS.result(); } LuaDefine(deque_setl, "deque,n,value", "set the nth item from the left end of a deque") { LuaArg deque, nn, val; LuaDefStack LS(L, deque, nn, val); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); int n = LS.ckint(nn); if ((n < 1) || (n > fill)) { luaL_error(L, "invalid index"); return LS.result(); } int target = (left + n - 1) & (max - 1); LS.rawset(deque, DEQUE_BASE + target, val); return LS.result(); } LuaDefine(deque_setr, "deque,n,value", "set the nth item from the right end of a deque") { LuaArg deque, nn, val; LuaDefStack LS(L, deque, nn, val); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); int n = LS.ckint(nn); if ((n < 1) || (n > fill)) { luaL_error(L, "invalid index"); return LS.result(); } int target = (left + fill - n) & (max - 1); LS.rawset(deque, DEQUE_BASE + target, val); return LS.result(); } LuaDefine(deque_findl, "deque,value", "find the first occurence of value in deque, starting from left") { LuaArg deque, val; LuaRet pos; LuaVar check; LuaDefStack LS(L, deque, val, pos, check); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); for (int i = 0; i < fill; i++) { int index = (left + i) & (max - 1); LS.rawget(check, deque, DEQUE_BASE + index); if (LS.rawequal(check, val)) { LS.set(pos, i + 1); return LS.result(); } } LS.set(pos, LuaNil); return LS.result(); } LuaDefine(deque_findr, "deque,value", "find the first occurrence of value in deque, starting from right") { LuaArg deque, val; LuaRet pos; LuaVar check; LuaDefStack LS(L, deque, val, pos, check); int left, fill, max; deque_get_info(L, deque.index(), &left, &fill, &max); int base = left + fill - 1; for (int i = 0; i < fill; i++) { int index = (base - i) & (max - 1); LS.rawget(check, deque, DEQUE_BASE + index); if (LS.rawequal(check, val)) { LS.set(pos, i + 1); return LS.result(); } } LS.set(pos, LuaNil); return LS.result(); } LuaDefine(deque_size, "deque", "return the number of items in the deque") { LuaArg deque; LuaRet size; LuaDefStack LS(L, deque, size); LS.cktable(deque, "deque"); LS.rawget(size, deque, DEQUE_FILL); if (!LS.tryinteger(size)) { luaL_error(L, "deque has been corrupted"); } return LS.result(); } ///////////////////////////////////////////////////////////// // // table_getpairs // // Given a table, return all the pairs in the table as a sequence. // The resulting sequence contains a 1 in the first position, // followed by the pairs, like so: // // sortedpairs({a=1,b=2,c=3}) => {1,"a",1,"b",2,"c",3} // // Note that this function is not directly exposed to lua. // It's exposed only through the 'sortedpairs' iterator. // ///////////////////////////////////////////////////////////// static void pushkey (lua_State *L, int tab, int i) { lua_rawgeti(L, tab, i*2); } static void push2keys (lua_State *L, int tab, int i, int j) { lua_rawgeti(L, tab, i*2); lua_rawgeti(L, tab, j*2); } static void pop2keys (lua_State *L, int tab, int i, int j) { lua_rawseti(L, tab, i*2); lua_rawseti(L, tab, j*2); } static void swap2values (lua_State *L, int tab, int i, int j) { lua_rawgeti(L, tab, i*2+1); lua_rawgeti(L, tab, j*2+1); lua_rawseti(L, tab, i*2+1); lua_rawseti(L, tab, j*2+1); } static void auxsort (lua_State *L, int tab, int l, int u) { while (l < u) { /* for tail recursion */ int i, j; /* sort elements a[l], a[(l+u)/2] and a[u] */ push2keys(L, tab, l, u); if (lua_genlt(L, -1, -2)) { pop2keys(L, tab, l, u); swap2values(L, tab, l, u); } else { lua_pop(L, 2); } if (u - l == 1) break; /* only 2 elements */ i = (l + u) / 2; push2keys(L, tab, i, l); if (lua_genlt(L, -2, -1)) { pop2keys(L, tab, i, l); swap2values(L, tab, i, l); } else { lua_pop(L, 1); /* remove a[l] */ pushkey(L, tab, u); if (lua_genlt(L, -1, -2)) { pop2keys(L, tab, i, u); swap2values(L, tab, i, u); } else { lua_pop(L, 2); } } if (u - l == 2) break; /* only 3 elements */ /* put the pivot value on top of the stack and keep it there */ pushkey(L, tab, i); /* move the pivot from i to u-1 */ lua_pushvalue(L, -1); pushkey(L, tab, u-1); pop2keys(L, tab, i, u-1); swap2values(L, tab, i, u-1); /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ i = l; j = u - 1; for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ /* repeat ++i until a[i] >= P */ while (pushkey(L, tab, ++i), lua_genlt(L, -1, -2)) { if (i >= u) luaL_error(L, "invalid order function for sorting"); lua_pop(L, 1); /* remove a[i] */ } /* repeat --j until a[j] <= P */ while (pushkey(L, tab, --j), lua_genlt(L, -3, -1)) { if (j <= l) luaL_error(L, "invalid order function for sorting"); lua_pop(L, 1); /* remove a[j] */ } if (j < i) { lua_pop(L, 3); /* pop pivot, a[i], a[j] */ break; } pop2keys(L, tab, i, j); swap2values(L, tab, i, j); } push2keys(L, tab, u-1, i); pop2keys(L, tab, u-1, i); swap2values(L, tab, u-1, i); /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ if (i - l < u - i) { j = l; i = i - 1; l = i + 2; } else { j = i + 1; i = u; u = j - 2; } auxsort(L, tab, j, i); /* call recursively the smaller one */ } /* repeat the routine for the larger one */ } bool table_getpairs(LuaCoreStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort) { lua_State *L = LS0.state(); LuaVar key, value; LuaExtStack LS(L, key, value); bool sorted = true; // Create the table, store the initial 1. int total = lua_nkeys(L, tab.index()); LS.createtable(pairs, total * 2 + 1, 0); LS.rawset(pairs, 1, 1); // Transfer the pairs into the sequence. lua_pushnil(L); int offset = 2; while (lua_next(L, tab.index()) != 0) { int ktype = lua_type(L, -2); if (ktype != LUA_TNUMBER && ktype != LUA_TSTRING && ktype != LUA_TBOOLEAN) { sorted = false; } lua_pushvalue(L, -2); // K V K lua_rawseti(L, pairs.index(), offset++); lua_rawseti(L, pairs.index(), offset++); } if (sort) { auxsort(L, pairs.index(), 1, total); } return sorted; } ///////////////////////////////////////////////////////////// // // Given a sortedpairs array, return the (key, value) pairs // one by one. The first element of the array is used as a // counter to keep track of our position. // ///////////////////////////////////////////////////////////// LuaDefine(table_nextsortedpair, "sortedpairs,dummy", "next function used by sortedpairs") { if (lua_gettop(L) < 2) { luaL_error(L, "Not enough arguments to nextpair"); } luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 1); lua_Integer i = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_pushinteger(L, i+1); lua_rawseti(L, 1, 1); lua_rawgeti(L, 1, i*2); if (lua_isnil(L, -1)) { return 1; } else { lua_rawgeti(L, 1, i*2+1); return 2; } } LuaDefine(table_sortedpairs, "table", "|Iterate over table, sorting all keys" "|" "|Some keys can't be sorted. For example, you can use a closure" "|as a table key. If you try to iterate over a table containing" "|a non-sortable key, the error 'Cannot sort the table keys' will" "|be generated." "|" "|See doc(genlt) for information about the sort order." "|" "|") { LuaArg tab; LuaRet closure, rtab, key; LuaDefStack LS(L, tab, closure, rtab, key); bool sorted = table_getpairs(LS, tab, rtab, true); if (!sorted) { luaL_error(L, "Cannot sort the table keys"); } LS.set(closure, lfn_table_nextsortedpair); LS.set(key, LuaNil); return LS.result(); } LuaDefine(table_semisortedpairs, "table", "|Iterate over table, sorting all the keys that can be sorted." "|" "|Some keys can't be sorted. For example, you can use a closure" "|as a table key. If you try to iterate over a table containing" "|a non-sortable key, the non-sortable elements will appear at" "|the end of the iteration, after all the sortable elements. The" "|non-sortable elements will be in an arbitrary order." "|" "|See doc(genlt) for information about the sort order." "|") { LuaArg tab; LuaRet closure, rtab, key; LuaDefStack LS(L, tab, closure, rtab, key); table_getpairs(LS, tab, rtab, true); LS.set(closure, lfn_table_nextsortedpair); LS.set(key, LuaNil); return LS.result(); } LuaDefine(genlt, "obj1,obj2", "|Generalized less-than function" "|" "|This comparison function can compare any two objects. The" "|return value is as follows:" "|" "|* Numbers are compared in the obvious numeric manner." "|* Strings are compared alphabetically." "|* Booleans are compared with false being less than true." "|* Tokens are compared alphabetically." "|* Tables are all considered equal to other tables." "|* Functions are all considered equal to other functions." "|* Coroutines are all considered equal to other coroutines." "|" "|Values of different types are printed in this order:" "|" "| Numbers, Strings, Booleans, Tokens," "| Functions, Threads, Tables, and NIL" "|") { LuaArg o1,o2; LuaRet lt; LuaDefStack LS(L, o1, o2, lt); int ltf = lua_genlt(L, o1.index(), o2.index()); LS.set(lt, ltf ? true:false); return LS.result(); }