260 lines
7.5 KiB
C++
260 lines
7.5 KiB
C++
#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<std::string> 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();
|
|
}
|
|
|
|
|