#include "luastack.hpp" #include "streambuffer.hpp" #include "tablecmp.hpp" // Given a table and an tnmap, return the table number of the table. // Returns zero if the table doesn't have a table number. // static int get_table_number(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap) { lua_State *L = MLS.state(); lua_pushvalue(L, mval.index()); lua_rawget(L, mtnmap.index()); int result = 0; if (lua_type(L, -1) == LUA_TNUMBER) { result = lua_tointeger(L, -1); } lua_pop(L, 1); return result; } // Get the tangible ID of a tangible. static int64_t get_tangible_id(const LuaStack &LS, LuaSlot tab) { int64_t id = 0; if (LS.istable(tab) && LS.gettabletype(tab) == LUA_TT_TANGIBLE) { lua_State *L = LS.state(); if (lua_getmetatable(L, tab.index())) { lua_pushstring(L, "id"); lua_rawget(L, -2); if (lua_type(L, -1) == LUA_TNUMBER) { id = lua_tointeger(L, -1); } lua_pop(L, 2); } } return id; } static bool equivalent_values(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, LuaStack &SLS, LuaSlot sval, LuaSlot stnmap) { switch (MLS.xtype(mval)) { case LUA_TBOOLEAN: { if (SLS.type(sval) != LUA_TBOOLEAN) return false; return MLS.ckboolean(mval) == SLS.ckboolean(sval); } case LUA_TNUMBER: { if (SLS.type(sval) != LUA_TNUMBER) return false; return MLS.cknumber(mval) == SLS.cknumber(sval); } case LUA_TSTRING: { // This could be faster if I used lua_tolstring directly. if (SLS.type(sval) != LUA_TSTRING) return false; return MLS.ckstring(mval) == SLS.ckstring(sval); } case LUA_TFUNCTION: { // Cannot really compare. Just return true if the types match. return SLS.type(sval) == MLS.type(mval); } case LUA_TT_GENERAL: { if (SLS.xtype(sval) != LUA_TT_GENERAL) return false; int midx = get_table_number(MLS, mval, mtnmap); if (midx == 0) return false; int sidx = get_table_number(SLS, sval, stnmap); if (sidx == 0) return false; return midx == sidx; } case LUA_TT_CLASS: { if (SLS.xtype(sval) != LUA_TT_CLASS) return false; return MLS.classname(mval) == SLS.classname(sval); } case LUA_TT_TANGIBLE: { if (SLS.xtype(sval) != LUA_TT_TANGIBLE) return false; return get_tangible_id(MLS, mval) == get_tangible_id(SLS, sval); } case LUA_TT_GLOBALENV: { return (SLS.xtype(sval) == LUA_TT_GLOBALENV); } default: // We're forcing anything else to go to NIL, // and once it's NIL, we consider it 'equal'. return SLS.type(sval) == LUA_TNIL; } } static void transmit_value(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBuffer *sb) { switch (MLS.xtype(mval)) { case LUA_TBOOLEAN: { sb->write_uint8(LUA_TBOOLEAN); sb->write_bool(MLS.ckboolean(mval)); return; } case LUA_TNUMBER: { sb->write_uint8(LUA_TNUMBER); sb->write_double(MLS.cknumber(mval)); return; } case LUA_TSTRING: { sb->write_uint8(LUA_TSTRING); sb->write_string(MLS.ckstring(mval)); return; } case LUA_TT_GENERAL: { sb->write_uint8(LUA_TT_GENERAL); sb->write_uint32(get_table_number(MLS, mval, mtnmap)); return; } case LUA_TT_CLASS: { sb->write_uint8(LUA_TT_CLASS); sb->write_string(MLS.classname(mval)); return; } case LUA_TT_TANGIBLE: { sb->write_uint8(LUA_TT_TANGIBLE); sb->write_int64(get_tangible_id(MLS, mval)); return; } case LUA_TT_GLOBALENV: { sb->write_uint8(LUA_TT_GLOBALENV); return; } default: sb->write_uint8(LUA_TNIL); return; } } bool tablecmp_diff(lua_State *synch, lua_State *master, bool cmeta, StreamBuffer *sb) { LuaArg mtnmap, mtab, stnmap, stab; LuaVar skey, mkey, sval, mval, mnil; LuaStack SLS(synch, stnmap, stab, skey, sval); LuaStack MLS(master, mtnmap, mtab, mkey, mval, mnil); MLS.set(mnil, LuaNil); int nupdates = 0; sb->write_int32(0); int wc = sb->total_writes(); MLS.set(mkey, LuaNil); while (MLS.next(mtab, mkey, mval)) { if (!MLS.issortablekey(mkey)) continue; MLS.movesortablekey(mkey, SLS, skey); SLS.rawget(sval, stab, skey); if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { transmit_value(MLS, mkey, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } SLS.set(skey, LuaNil); while (SLS.next(stab, skey, sval)) { if (!SLS.issortablekey(skey)) continue; SLS.movesortablekey(skey, MLS, mkey); MLS.rawget(mval, mtab, mkey); if (MLS.isnil(mval)) { transmit_value(MLS, mkey, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } if (cmeta) { SLS.getmetatable(sval, stab); MLS.getmetatable(mval, mtab); if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { transmit_value(MLS, mnil, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } sb->overwrite_int32(wc, nupdates); SLS.result(); MLS.result(); return (nupdates > 0); } static void tablecmp_value_debug_string(StreamBuffer *sb, std::ostringstream &oss) { int kind = sb->read_uint8(); switch (kind) { case LUA_TBOOLEAN: { bool b = sb->read_bool(); oss << (b ? "true":"false"); return; } case LUA_TNUMBER: { oss << sb->read_double(); return; } case LUA_TSTRING: { oss << sb->read_string(); return; } case LUA_TT_GENERAL: { oss << "table " << sb->read_int32(); return; } case LUA_TT_CLASS: { oss << "class " << sb->read_string(); return; } case LUA_TT_TANGIBLE: { oss << "tan " << sb->read_int64(); return; } case LUA_TT_GLOBALENV: { oss << "globals"; return; } case LUA_TNIL: { oss << "nil"; return; } default: assert(false); // Should not get here. } } std::string tablecmp_debug_string(StreamBuffer *sb) { std::vector sorted; std::ostringstream oss; int ndiffs = sb->read_int32(); for (int i = 0; i < ndiffs; i++) { tablecmp_value_debug_string(sb, oss); oss << "="; tablecmp_value_debug_string(sb, oss); sorted.push_back(oss.str()); oss.str(""); } std::sort(sorted.begin(), sorted.end()); for (const std::string &s : sorted) { oss << s << ";"; } return oss.str(); } LuaDefine(table_diffcompare, "c") { LuaArg mtnmap, mtab, stnmap, stab; LuaRet dbgstring; LuaVar tthread; LuaStack LS(L, mtnmap, mtab, stnmap, stab, dbgstring, tthread); // Create a temporary thread to be the 'synch model'. We'll use the // existing thread as the 'master model'. lua_State *synch = lua_newthread(L); lua_replace(L, tthread.index()); // Call tablecmp_diff. StreamBuffer sb; lua_pushvalue(L, stnmap.index()); lua_pushvalue(L, stab.index()); lua_xmove(L, synch, 2); lua_pushvalue(L, mtnmap.index()); lua_pushvalue(L, mtab.index()); tablecmp_diff(synch, L, true, &sb); // Convert the output to a debug string. LS.set(dbgstring, tablecmp_debug_string(&sb)); return LS.result(); }