Files
integration/luprex/cpp/core/pprint.cpp

401 lines
13 KiB
C++
Raw Normal View History

#include <ostream>
#include "pprint.hpp"
#include "util.hpp"
2021-09-08 01:32:08 -04:00
#include "table.hpp"
#include <cmath>
class PrintMachine {
public:
LuaVar tabchpos_;
LuaExtStack LS_;
int next_id_;
bool indent_;
std::ostream *output_;
// 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) {
case LUA_TNIL: {
(*output_) << "nil";
return;
}
case LUA_TSTRING: {
if (quote) {
util::quote_string(LS_.ckstring(val), output_);
} else {
// TODO: this could be more efficient.
(*output_) << LS_.ckstring(val);
}
return;
}
case LUA_TNUMBER: {
double value = LS_.cknumber(val);
if (std::isnan(value)) {
(*output_) << "nan";
} else {
int64_t ivalue = int64_t(value);
if (double(ivalue) == value) {
(*output_) << ivalue;
} else {
(*output_) << value;
}
}
return;
2021-12-14 10:51:00 -05:00
}
case LUA_TBOOLEAN: {
(*output_) << (LS_.ckboolean(val) ? "true" : "false");
return;
}
case LUA_TFUNCTION: {
(*output_) << "<function>";
return;
}
case LUA_TTHREAD: {
(*output_) << "<thread>";
return;
}
case LUA_TLIGHTUSERDATA: {
LuaToken token = LS_.cktoken(val);
(*output_) << "[" << token.str() << "]";
return;
}
case LUA_TT_GENERAL: {
eng::string classname = LS_.classname(val);
if (classname.empty()) {
(*output_) << "<table>";
} else {
(*output_) << "<" << classname << ">";
}
return;
}
case LUA_TT_TANGIBLE: {
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: {
(*output_) << "<class " << LS_.classname(val) << ">";
return;
}
case LUA_TT_GLOBALENV: {
(*output_) << "<global-env>";
return;
}
case LUA_TT_TANGIBLEMETA: {
(*output_) << "<tangible-metatable>";
return;
}
default: {
(*output_) << "<unknown type #" << xtype << ">";
return;
}}
2021-09-08 01:32:08 -04:00
}
void tabify(int level) {
if (indent_) {
(*output_) << std::endl;
for (int i = 0; i < level; i++) {
(*output_) << " ";
2021-09-08 01:32:08 -04:00
}
} else {
(*output_) << " ";
2021-09-08 01:32:08 -04:00
}
}
void pprint_r(int level, bool expand, LuaSlot value) {
lua_State *L = LS_.state();
lua_checkstack(L, 20);
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);
2021-09-08 01:32:08 -04:00
// 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);
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.
}
2021-09-08 01:32:08 -04:00
}
// 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;
}
}
}
// 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);
}
(*output_) << "}";
}
2021-09-08 01:32:08 -04:00
// 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);
2025-01-20 21:11:59 -05:00
} else if (LS.istoken(key)) {
atomic_print(LUA_TLIGHTUSERDATA, key, false);
} else {
(*output_) << "[";
pprint_r(level + 1, false, key);
(*output_) << "]";
}
if (indent_) {
(*output_) << " = ";
} else {
(*output_) << "=";
}
pprint_r(level + 1, false, val);
}
if (print_meta) {
LS.getmetatable(val, value);
if (needcomma) (*output_) << ",";
tabify(level + 1);
(*output_) << "<meta> = ";
pprint_r(level + 1, false, val);
}
tabify(level);
(*output_) << "}";
}
2021-09-08 01:32:08 -04:00
}
// Atomic print interface.
PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool quote, std::ostream *os) :
LS_(LS0.state(), tabchpos_) {
output_ = os;
atomic_print(LS_.xtype(root), root, quote);
}
2021-09-08 01:32:08 -04:00
// Pretty print interface.
PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool indent, int level, bool expand, std::ostream *os) :
LS_(LS0.state(), tabchpos_) {
next_id_ = 1;
indent_ = indent;
LS_.newtable(tabchpos_);
util::ostringstream preoutput;
output_ = &preoutput;
pprint_r(level, expand, root);
std::string_view pre = preoutput.view();
// Output the results. We would just copy the characters
// one by one to the target stream, except that we have to
// 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) << iter->second;
iter++;
2021-09-08 01:32:08 -04:00
}
(*os).put(pre[i]);
2021-09-08 01:32:08 -04:00
}
}
};
2021-09-08 01:32:08 -04:00
void PrettyPrintOptions::parse(LuaKeywordParser &kp) {
LuaVar option;
LuaExtStack LS(kp.state(), option);
kp.check_throw();
if (kp.optional(option, "indent")) {
indent = LS.ckboolean(option);
2021-09-08 01:32:08 -04:00
}
if (kp.optional(option, "level")) {
level = LS.ckint(option);
}
if (kp.optional(option, "expand")) {
expand = LS.ckboolean(option);
2021-09-08 01:32:08 -04:00
}
}
void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os) {
PrintMachine pm(LS, val, quote, os);
2021-09-08 01:32:08 -04:00
}
void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os) {
PrintMachine pm(LS, val, opts.indent, opts.level, opts.expand, os);
}
LuaDefine(string_pprint, "obj1, obj2, ...",
"|Pretty-print the specified objects into a string."
"|"
"|See also: string.pprintx, which has a lot more options."
"|This function uses the default options: pretty print indented,"
"|start at indentation level zero, and always expand the"
"|top-level table."
"|") {
2023-04-13 13:26:45 -04:00
LuaRet result;
LuaExtraArgs extra;
LuaDefStack LS(L, result, extra);
util::ostringstream oss;
2023-04-13 13:26:45 -04:00
for (int i = 0; i < extra.size(); i++) {
pprint(LS, extra[i], PrettyPrintOptions(), &oss);
oss << "\n";
}
LS.set(result, oss.view());
2023-04-13 13:26:45 -04:00
return LS.result();
}
LuaDefine(string_pprintx, "options",
"|Pretty-print the specified object into a string, with options"
"|"
"|Options is a table with these fields:"
"|"
"| value - the object to pretty-print"
"| indent - if false, suppress newlines and indentation (default: true)"
"| level - base level of indentation (default: zero)"
"| expand - if true, force expansion of top-level table (default: false)"
"|"
"|About the expand flag: normally, when you print a class, it just "
"|prints '<class name>', and when you print a tangible, it just"
"|prints '<tangible id>'. But sometimes, you want to see the details."
"|The expand flag forces it to expand the top-level table, even if the"
"|top-level table is a tangible or class."
"|") {
LuaArg loptions;
LuaRet result;
LuaVar value;
LuaDefStack LS(L, loptions, result, value);
PrettyPrintOptions options;
LuaKeywordParser kp(LS, loptions);
options.parse(kp);
if (!kp.optional(value, "value")) {
LS.set(value, LuaNil);
}
kp.final_check_throw();
util::ostringstream oss;
pprint(LS, value, options, &oss);
LS.set(result, oss.view());
return LS.result();
}
2021-09-08 01:32:08 -04:00
LuaDefine(string_print, "obj",
"|Concise print the specified object into a string"
"|"
"|This prints a concise representation of obj into a string. Tables"
2023-04-14 14:52:44 -04:00
"|are not expanded: for that, use string.pprint or string.pprintx."
"|"
2023-04-14 14:52:44 -04:00
"|The functions string.print and tostring are identical."
"|") {
LuaArg val;
LuaRet result;
LuaDefStack LS(L, val, result);
eng::ostringstream oss;
2021-12-15 23:03:43 -05:00
atomic_print(LS, val, false, &oss);
LS.set(result, oss.str());
return LS.result();
}
2023-04-14 14:52:44 -04:00
LuaDefineAlias(tostring, string_print);
LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") {
LuaArg str;
LuaRet result;
LuaDefStack LS(L, str, result);
if (LS.isstring(str)) {
eng::string s = LS.ckstring(str);
LS.set(result, sv::is_lua_id(s));
} else {
LS.set(result, false);
}
return LS.result();
}