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 { eng::string LuaCoreStack::classname(LuaSlot input) const {
LuaVar lookup, classtab, classname; LuaVar lookup, classtab, classname, metatable;
LuaExtStack LS(L_, lookup, classtab, classname); LuaExtStack LS(L_, lookup, classtab, classname, metatable);
int xt = xtype(input); int xt = xtype(input);
if (xt == LUA_TSTRING) { if (xt == LUA_TSTRING) {
@@ -377,6 +377,14 @@ eng::string LuaCoreStack::classname(LuaSlot input) const {
return ""; return "";
} }
return LS.ckstring(classname); 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 { } else {
return ""; return "";
} }

View File

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