Improvements to pretty-printer, including code to support printing classnames of tables

This commit is contained in:
2025-03-03 17:07:54 -05:00
parent 3da5e7534a
commit 551b3fb6b1
2 changed files with 134 additions and 81 deletions

View File

@@ -345,8 +345,8 @@ lua_State *LuaCoreStack::newthread(LuaSlot target) const {
}
eng::string LuaCoreStack::classname(LuaSlot input) const {
LuaVar lookup, classtab, classname;
LuaExtStack LS(L_, lookup, classtab, classname);
LuaVar lookup, classtab, classname, metatable;
LuaExtStack LS(L_, lookup, classtab, classname, metatable);
int xt = xtype(input);
if (xt == LUA_TSTRING) {
@@ -377,6 +377,14 @@ eng::string LuaCoreStack::classname(LuaSlot input) const {
return "";
}
return LS.ckstring(classname);
} else if (xt == LUA_TT_GENERAL) {
LS.getmetatable(metatable, input);
LS.rawget(lookup, LuaRegistry, "classnames");
LS.rawget(classname, lookup, metatable);
if (!LS.isstring(classname)) {
return "";
}
return LS.ckstring(classname);
} else {
return "";
}

View File

@@ -13,7 +13,12 @@ public:
int next_id_;
bool indent_;
std::ostream *output_;
eng::map<int, int> chpos_to_tabnum_;
// When a general table appears in the output, we use the character
// position where the table first appeared as a unique ID for the
// general table.
eng::map<int, int> chpos_to_table_number_;
eng::map<int, eng::string> chpos_to_header_;
void atomic_print(int xtype, LuaSlot val, bool quote) {
switch (xtype) {
@@ -62,11 +67,21 @@ public:
return;
}
case LUA_TT_GENERAL: {
(*output_) << "<table>";
eng::string classname = LS_.classname(val);
if (classname.empty()) {
(*output_) << "<table>";
} else {
(*output_) << "<" << classname << ">";
}
return;
}
case LUA_TT_TANGIBLE: {
(*output_) << "<tangible " << LS_.tanid(val) << ">";
eng::string classname = LS_.classname(val);
if (classname.empty()) {
(*output_) << "<tan " << LS_.tanid(val) << ">";
} else {
(*output_) << "<tan " << classname << " " << LS_.tanid(val) << ">";
}
return;
}
case LUA_TT_CLASS: {
@@ -101,75 +116,114 @@ public:
void pprint_r(int level, bool expand, LuaSlot value) {
lua_State *L = LS_.state();
lua_checkstack(L, 20);
LuaVar loffset, pairs, key, val, lchpos, nextseq;
LuaExtStack LS(L, loffset, pairs, key, val, lchpos, nextseq);
LuaVar loffset, pairs, key, val, lchpos;
LuaExtStack LS(L, loffset, pairs, key, val, lchpos);
// Determine the extended type of the object. If the
// expand flag is true, try to coerce it to a general table.
int xtype = LS.xtype(value);
eng::string classname = LS.classname(value);
// Print the atomic portion.
if (xtype != LUA_TT_GENERAL) {
// Print the atomic portion or table header.
// Make a decision about whether to print key-value pairs,
// if not, then return.
if (xtype < LUA_TT_GENERAL) {
atomic_print(xtype, value, true);
if ((xtype < LUA_TT_GENERAL) || (!expand)) {
return;
return;
} else if (xtype > LUA_TT_GENERAL) {
atomic_print(xtype, value, true);
if (!expand) return;
} else {
LS.rawget(lchpos, tabchpos_, value);
int chpos = LS.tryint(lchpos).value_or(-1);
if (chpos < 0) {
// First time.
// * The character position where the table first appears
// serves as a unique ID for the table. Record it in the tabchpos
// map. Then, generate an initial tentative header for the table,
// and insert it into the header map. Do not output the header to
// output stream: it will be post-injected into the output stream.
chpos = int((*output_).tellp());
LS.rawset(tabchpos_, value, chpos);
if (!classname.empty()) {
chpos_to_header_[chpos] = util::ss("<", classname, ">");
}
} else {
// Second time.
// See if the table already has a table number assigned. If not,
// assign it one. Update the header for the table.
// Do output the header to the output stream, it is only
// post-injected at the first table appearance.
int tabnum = chpos_to_table_number_[chpos];
if (tabnum == 0) {
tabnum = next_id_++;
chpos_to_table_number_[chpos] = tabnum;
if (classname.empty()) {
chpos_to_header_[chpos] = util::ss("<table ", tabnum, ">");
} else {
chpos_to_header_[chpos] = util::ss("<", classname, " ", tabnum, ">");
}
}
(*output_) << chpos_to_header_[chpos];
return; // Do not output key-value pairs the second time.
}
}
// Find out whether it has a table number. If necessary,
// assign one.
int tabnum = 0;
LS.rawget(lchpos, tabchpos_, value);
auto chpos = LS.tryint(lchpos);
if (!chpos) {
// First time. Record the character position where the
// table first appears in the output stream.
LS.rawset(tabchpos_, value, int((*output_).tellp()));
} else {
tabnum = chpos_to_tabnum_[*chpos];
if (tabnum == 0) {
// Second time. The table is already in the output,
// but it hasn't been assigned a number. Assign one.
tabnum = next_id_++;
chpos_to_tabnum_[*chpos] = tabnum;
// How many keys in the table?
int nkeys = LS.nkeys(value);
// Decide whether we're going to print a line for the metatable.
// If the table has a classname, we don't need, to, because the
// classname (which is in the header) tell the reader what metatable
// is being used. Also, tangible metatables are not shown, because
// tangibles keep secret stuff in the metatable. (this may change).
bool print_meta = false;
if (classname.empty()) {
LS.getmetatable(val, value);
if (LS.istable(val) && (LS.gettabletype(val) != LUA_TT_TANGIBLEMETA)) {
print_meta = true;
}
}
// If it's an array of atomic values, without a visible metatable,
// we're going to print it without newlines. Scan the table to see if
// it's possible to print it array-style.
bool array_style = false;
if (!print_meta) {
array_style = true;
for (int i = 1; i <= nkeys; i++) {
LS.rawget(val, value, i);
if (LS.isnil(val) || (LS.type(val) == LUA_TTABLE)) {
array_style = false;
break;
}
}
}
// If the table has a number, that means we're visiting
// it for the second time. Just print an abbreviated version
// and return.
if (tabnum > 0) {
(*output_) << "<table " << tabnum << ">";
if (lua_nkeys(L, value.index())==0) {
(*output_) << "{}";
} else {
(*output_) << "{...}";
// Print it array-style. This code is simple because
// we don't do indentation, and we don't handle any values
// that aren't atomic.
if (array_style) {
(*output_) << "{";
for (int i = 1; i <= nkeys; i++) {
if (i > 1) (*output_) << ", ";
LS.rawget(val, value, i);
atomic_print(LS.xtype(val), val, true);
}
return;
(*output_) << "}";
}
// State variables.
bool needcomma = false;
bool multiline = false;
LS.set(nextseq, 1);
// Open the brackets.
(*output_) << "{";
// Output the table keys.
table_getpairs(LS, value, pairs, true);
for (int i = 2; ; i+=2) {
LS.rawget(key, pairs, i);
if (LS.isnil(key)) break;
LS.rawget(val, pairs, i+1);
if (needcomma) (*output_) << ",";
needcomma = true;
if (LS.rawequal(key, nextseq)) {
(*output_) << " ";
pprint_r(level + 1, false, val);
LS.set(nextseq, LS.ckinteger(nextseq) + 1);
} else {
multiline = true;
// Print it table-style.
if (!array_style) {
table_getpairs(LS, value, pairs, true);
(*output_) << "{";
bool needcomma = false;
for (int i = 2; ; i+=2) {
LS.rawget(key, pairs, i);
if (LS.isnil(key)) break;
LS.rawget(val, pairs, i+1);
if (needcomma) (*output_) << ",";
needcomma = true;
tabify(level + 1);
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
(*output_) << LS.ckstring(key);
@@ -187,26 +241,16 @@ public:
}
pprint_r(level + 1, false, val);
}
}
// Output the metatable.
LS.getmetatable(val, value);
if (LS.istable(val) && (LS.gettabletype(val) != LUA_TT_TANGIBLEMETA)) {
multiline = true;
if (needcomma) (*output_) << ",";
needcomma = true;
tabify(level + 1);
(*output_) << "<meta> = ";
pprint_r(level + 1, false, val);
}
// Close the brackets.
if (multiline) {
if (print_meta) {
LS.getmetatable(val, value);
if (needcomma) (*output_) << ",";
tabify(level + 1);
(*output_) << "<meta> = ";
pprint_r(level + 1, false, val);
}
tabify(level);
} else if (LS.ckinteger(nextseq) > 1) {
(*output_) << " ";
(*output_) << "}";
}
(*output_) << "}";
}
// Atomic print interface.
@@ -229,12 +273,13 @@ public:
// Output the results. We would just copy the characters
// one by one to the target stream, except that we have to
// insert <table XX> in front of all tables that got referenced.
chpos_to_tabnum_.emplace(0x7FFFFFFF, 0);
auto iter = chpos_to_tabnum_.begin();
// insert table headers wherever the chpos_to_header map says
// so.
chpos_to_header_.emplace(0x7FFFFFFF, "");
auto iter = chpos_to_header_.begin();
for (int i = 0; i < int(pre.size()); i++) {
if (i == iter->first) {
(*os) << "<table " << iter->second << ">";
(*os) << iter->second;
iter++;
}
(*os).put(pre[i]);