From 551b3fb6b1874865f78e1f2d25af536f1b29c8af Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 3 Mar 2025 17:07:54 -0500 Subject: [PATCH] Improvements to pretty-printer, including code to support printing classnames of tables --- luprex/cpp/core/luastack.cpp | 12 ++- luprex/cpp/core/pprint.cpp | 203 +++++++++++++++++++++-------------- 2 files changed, 134 insertions(+), 81 deletions(-) diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index 618c65cd..c3ffc76a 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -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 ""; } diff --git a/luprex/cpp/core/pprint.cpp b/luprex/cpp/core/pprint.cpp index 3f9016d5..e5a07f08 100644 --- a/luprex/cpp/core/pprint.cpp +++ b/luprex/cpp/core/pprint.cpp @@ -13,7 +13,12 @@ public: int next_id_; bool indent_; std::ostream *output_; - eng::map 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 chpos_to_table_number_; + eng::map 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_) << ""; + eng::string classname = LS_.classname(val); + if (classname.empty()) { + (*output_) << "
"; + } else { + (*output_) << "<" << classname << ">"; + } return; } case LUA_TT_TANGIBLE: { - (*output_) << ""; + eng::string classname = LS_.classname(val); + if (classname.empty()) { + (*output_) << ""; + } else { + (*output_) << ""; + } 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("
"); + } 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_) << "
"; - 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_) << " = "; - 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_) << " = "; + 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
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) << "
second << ">"; + (*os) << iter->second; iter++; } (*os).put(pre[i]);