Files
integration/luprex/core/cpp/world-difftab.cpp

501 lines
15 KiB
C++

////////////////////////////////////////////////////////////////////
//
// This file contains the code to compare the contents of tables.
// The top level functions in this file are:
//
// World::diff_numbered_tables
// World::diff_tangible_databases
// World::patch_numbered_tables
// World::patch_tangible_databases
//
// It also contains these unit testing support routines:
//
// table.diffcompare
// table.diffapply
//
// This file also contains all the support code needed to implement
// this stuff.
//
////////////////////////////////////////////////////////////////////
#include "luastack.hpp"
#include "streambuffer.hpp"
#include "table.hpp"
#include "world.hpp"
#include <iostream>
// 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;
}
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: {
int midx = get_table_number(MLS, mval, mtnmap);
if (midx == 0) {
return SLS.isnil(sval);
}
int sidx = get_table_number(SLS, sval, stnmap);
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 World::tangible_id(MLS, mval) == World::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: {
int midx = get_table_number(MLS, mval, mtnmap);
if (midx == 0) {
sb->write_uint8(LUA_TNIL);
} else {
sb->write_uint8(LUA_TT_GENERAL);
sb->write_uint32(midx);
}
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(World::tangible_id(MLS, mval));
return;
}
case LUA_TT_GLOBALENV: {
sb->write_uint8(LUA_TT_GLOBALENV);
return;
}
default:
sb->write_uint8(LUA_TNIL);
return;
}
}
static void transmit_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.
}
}
static bool diff_tables(LuaStack &SLS0, LuaSlot stnmap, LuaSlot stab,
LuaStack &MLS0, LuaSlot mtnmap, LuaSlot mtab,
bool cmeta, StreamBuffer *sb) {
LuaVar skey, mkey, sval, mval, mnil;
LuaStack SLS(SLS0.state(), skey, sval);
LuaStack MLS(MLS0.state(), mkey, mval, mnil);
assert(MLS.istable(mtab));
assert(SLS.istable(stab));
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 std::string diff_tables_debug_string(StreamBuffer *sb) {
std::vector<std::string> sorted;
std::ostringstream oss;
int ndiffs = sb->read_int32();
for (int i = 0; i < ndiffs; i++) {
transmit_value_debug_string(sb, oss);
oss << "=";
transmit_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();
}
static void set_transmitted_value(LuaStack &LS, LuaSlot tangibles, LuaSlot ntmap, LuaSlot target, StreamBuffer *sb) {
int kind = sb->read_uint8();
switch (kind) {
case LUA_TBOOLEAN: {
LS.set(target, sb->read_bool());
return;
}
case LUA_TNUMBER: {
LS.set(target, sb->read_double());
return;
}
case LUA_TSTRING: {
LS.set(target, sb->read_string());
return;
}
case LUA_TT_GENERAL: {
LS.rawgeti(target, ntmap, sb->read_int32());
return;
}
case LUA_TT_CLASS: {
LS.makeclass(target, sb->read_string());
return;
}
case LUA_TT_TANGIBLE: {
int64_t id = sb->read_int64();
LS.rawgeti(target, tangibles, id);
if (LS.isnil(target)) {
World *w = World::fetch_global_pointer(LS.state());
w->tangible_make(LS.state(), id, "nowhere", true);
lua_replace(LS.state(), target.index());
}
return;
}
case LUA_TT_GLOBALENV: {
LS.getglobaltable(target);
return;
}
case LUA_TNIL: {
LS.set(target, LuaNil);
return;
}
default:
assert(false); // Should not get here.
}
}
static void patch_table(LuaStack &LS0, LuaSlot tangibles, LuaSlot ntmap, LuaSlot tab, StreamBuffer *sb) {
LuaVar key, val;
LuaStack LS(LS0.state(), key, val);
int ndiffs = sb->read_int32();
for (int i = 0; i < ndiffs; i++) {
set_transmitted_value(LS, tangibles, ntmap, key, sb);
set_transmitted_value(LS, tangibles, ntmap, val, sb);
if (LS.isnil(key)) {
LS.setmetatable(tab, val);
} else {
LS.rawset(tab, key, val);
}
}
LS.result();
}
void World::patch_numbered_tables(StreamBuffer *sb) {
lua_State *L = state();
LuaVar tangibles, ntmap, tab;
LuaStack LS(L, tangibles, ntmap, tab);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(ntmap, LuaRegistry, "ntmap");
assert(LS.istable(tangibles));
assert(LS.istable(ntmap));
int nmodified = sb->read_int32();
for (int i = 0; i < nmodified; i++) {
int index = sb->read_int32();
LS.rawgeti(tab, ntmap, index);
assert(LS.istable(tab));
patch_table(LS, tangibles, ntmap, tab, sb);
}
LS.result();
}
void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) {
lua_State *synch = state();
LuaVar sntmap, mntmap, stnmap, mtnmap, stab, mtab;
LuaStack SLS(synch, sntmap, stnmap, stab);
LuaStack MLS(master, mntmap, mtnmap, mtab);
SLS.rawget(sntmap, LuaRegistry, "ntmap");
MLS.rawget(mntmap, LuaRegistry, "ntmap");
SLS.rawget(stnmap, LuaRegistry, "tnmap");
MLS.rawget(mtnmap, LuaRegistry, "tnmap");
int m_ntables = MLS.rawlen(mntmap);
int s_ntables = SLS.rawlen(sntmap);
assert(m_ntables == s_ntables);
sb->write_int32(0);
int write_count_after = sb->total_writes();
int nmodified = 0;
int s_top = lua_gettop(synch);
int m_top = lua_gettop(master);
for (int id = 1; id <= m_ntables; id++) {
MLS.rawgeti(mtab, mntmap, id);
if (MLS.istable(mtab)) {
SLS.rawgeti(stab, sntmap, id);
assert(SLS.istable(stab));
int tw = sb->total_writes();
sb->write_int32(id);
nmodified += 1;
if (!diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, sb)) {
sb->unwrite_to(tw);
nmodified -= 1;
}
}
assert(lua_gettop(synch) == s_top);
assert(lua_gettop(master) == m_top);
}
sb->overwrite_int32(write_count_after, nmodified);
SLS.result();
MLS.result();
}
void World::patch_tangible_databases(StreamBuffer *sb) {
lua_State *L = state();
LuaVar tangibles, ntmap, tab;
LuaStack LS(L, tangibles, ntmap, tab);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(ntmap, LuaRegistry, "ntmap");
assert(LS.istable(tangibles));
assert(LS.istable(ntmap));
int nmodified = sb->read_int32();
for (int i = 0; i < nmodified; i++) {
int64_t id = sb->read_int64();
LS.rawgeti(tab, tangibles, id);
assert(LS.istable(tab));
patch_table(LS, tangibles, ntmap, tab, sb);
}
LS.result();
}
void World::diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb) {
lua_State *synch = state();
LuaVar stnmap, mtnmap, stangibles, mtangibles, stab, mtab;
LuaStack SLS(synch, stnmap, stangibles, stab);
LuaStack MLS(master, mtnmap, mtangibles, mtab);
SLS.rawget(stnmap, LuaRegistry, "tnmap");
MLS.rawget(mtnmap, LuaRegistry, "tnmap");
SLS.rawget(stangibles, LuaRegistry, "tangibles");
MLS.rawget(mtangibles, LuaRegistry, "tangibles");
sb->write_int32(0);
int write_count_after = sb->total_writes();
int nmodified = 0;
int s_top = lua_gettop(synch);
int m_top = lua_gettop(master);
for (int64_t id : basis) {
MLS.rawget(mtab, mtangibles, id);
SLS.rawget(stab, stangibles, id);
assert(MLS.istable(mtab));
assert(SLS.istable(stab));
int tw = sb->total_writes();
sb->write_int64(id);
nmodified += 1;
if (!diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, false, sb)) {
sb->unwrite_to(tw);
nmodified -= 1;
}
assert(lua_gettop(synch) == s_top);
assert(lua_gettop(master) == m_top);
}
sb->overwrite_int32(write_count_after, nmodified);
SLS.result();
MLS.result();
}
LuaDefine(table_diffcompare, "c") {
LuaArg mtnmap, mtab, mstnmap, mstab, stnmap, stab;
LuaRet dbgstring;
LuaVar tthread;
LuaStack MLS(L, mtnmap, mtab, mstnmap, mstab, dbgstring, tthread);
// Check the arguments.
MLS.checktable(mtnmap);
MLS.checktable(mstnmap);
MLS.checktable(mtab);
MLS.checktable(mstab);
// Create a temporary thread to be the 'synch model'. We'll use the
// existing thread as the 'master model'. Move two tables to the synch thread.
lua_State *synch = lua_newthread(L);
lua_replace(L, tthread.index());
lua_pushvalue(L, mstnmap.index());
lua_pushvalue(L, mstab.index());
lua_xmove(L, synch, 2);
LuaStack SLS(synch, stnmap, stab);
// Call tablecmp_diff.
StreamBuffer sb;
diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb);
// Convert the output to a debug string.
MLS.set(dbgstring, diff_tables_debug_string(&sb));
return MLS.result();
}
LuaDefine(table_diffapply, "c") {
LuaArg mtnmap, mtab, mstab, stnmap, stab;
LuaRet eql, eqlstr, rtab;
LuaVar tthread, tangibles, mntmap, key, val;
LuaStack MLS(L, mtnmap, mtab, mstab, eql, eqlstr, rtab, tthread, tangibles, mntmap, key, val);
// Check the arguments.
MLS.checktable(mtnmap);
MLS.checktable(mtab);
MLS.checktable(mstab);
// Get the tangibles map.
MLS.rawget(tangibles, LuaRegistry, "tangibles");
// Invert the tnmap to make the ntmap.
MLS.set(mntmap, LuaNewTable);
MLS.set(key, LuaNil);
while (MLS.next(mtnmap, key, val)) {
MLS.rawset(mntmap, val, key);
}
// 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());
lua_pushvalue(L, mtnmap.index());
lua_pushvalue(L, mstab.index());
lua_xmove(L, synch, 2);
LuaStack SLS(synch, stnmap, stab);
// Call diff_tables and patch_tables
StreamBuffer sb;
diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb);
patch_table(MLS, tangibles, mntmap, mstab, &sb);
bool eq = table_equal(MLS, mstab, mtab);
MLS.set(eql, eq);
if (eq) {
MLS.set(eqlstr, "tables equal");
} else {
MLS.set(eqlstr, "tables were supposed to be equal");
}
MLS.set(rtab, stab);
return MLS.result();
}