Fix util::ostringstream, fix pretty-printing, stub out new globals

This commit is contained in:
2023-03-05 01:51:25 -05:00
parent db234c2934
commit 86a27ef2d4
13 changed files with 455 additions and 282 deletions

View File

@@ -7,255 +7,352 @@
#include <iostream>
#include <cmath>
class PrintMachine {
public:
LuaVar tabchpos_;
LuaStack LS_;
int next_id_;
bool indent_;
std::ostream *output_;
eng::map<int, int> chpos_to_tabnum_;
void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
int tt = LS.type(val);
switch (tt) {
case LUA_TNIL:
(*os) << "nil";
return;
case LUA_TSTRING:
if (quote) {
util::quote_string(LS.ckstring(val), os);
} else {
// TODO: this could be more efficient.
(*os) << LS.ckstring(val);
}
return;
case LUA_TNUMBER: {
double value = LS.cknumber(val);
if (std::isnan(value)) {
(*os) << "nan";
} else {
int64_t ivalue = int64_t(value);
if (double(ivalue) == value) {
(*os) << ivalue;
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 {
(*os) << value;
}
// TODO: this could be more efficient.
(*output_) << LS_.ckstring(val);
}
return;
}
return;
}
case LUA_TBOOLEAN:
(*os) << (LS.ckboolean(val) ? "true" : "false");
return;
case LUA_TFUNCTION: {
(*os) << "<function>";
return;
}
case LUA_TLIGHTUSERDATA: {
LuaToken token = LS.cktoken(val);
(*os) << "[" << token.str() << "]";
return;
}
default:
(*os) << "<" << lua_typename(LS.state(), tt) << ">";
return;
}
}
// Find tables recursively.
//
// Builds a table (tabcount) whose keys are tables. If a table
// is visited exactly once, then tabcount[t]=0. If a table is
// visited more than once, then tabcount[t]=-1.
//
static void findtables(LuaStack &LS0, LuaSlot root, LuaSlot tabcount) {
lua_State *L = LS0.state();
LuaVar key, val, tab, count;
LuaStack LS(L, key, val, tab, count);
LS.newtable(tabcount);
int top = lua_gettop(L);
if (LS.istable(root)) {
lua_pushvalue(L, root.index());
}
while (lua_gettop(L) > top) {
lua_checkstack(L, 20);
lua_replace(L, tab.index());
LS.rawget(count, tabcount, tab);
if (LS.isnil(count)) {
LS.rawset(tabcount, tab, 0);
LS.set(key, LuaNil);
while (LS.next(tab, key, val)) {
lua_checkstack(L, 20);
if (LS.istable(key)) {
lua_pushvalue(L, key.index());
}
if (LS.istable(val)) {
lua_pushvalue(L, val.index());
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;
}
}
LS.getmetatable(val, tab);
if (LS.istable(val)) {
lua_pushvalue(L, val.index());
return;
}
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: {
(*output_) << "<table>";
return;
}
case LUA_TT_TANGIBLE: {
(*output_) << "<tangible " << LS_.tanid(val) << ">";
return;
}
case LUA_TT_CLASS: {
(*output_) << "<class " << LS_.classname(val) << ">";
return;
}
case LUA_TT_GLOBALENV: {
(*output_) << "<global-env>";
return;
}
default: {
(*output_) << "<unknown type #" << xtype << ">";
return;
}}
}
void tabify(int level) {
if (indent_) {
(*output_) << std::endl;
for (int i = 0; i < level; i++) {
(*output_) << " ";
}
} else {
LS.rawset(tabcount, tab, -1);
(*output_) << " ";
}
}
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;
LuaStack LS(L, loffset, pairs, key, val, lchpos, nextseq);
// Determine the extended type of the object. If the
// expand flag is true, try to coerce it to a general table.
int type = LS.xtype(value);
if (expand && (LS.istable(value))) {
type = LUA_TT_GENERAL;
}
// If it's anything but a general table, use 'atomic_print'
// and return.
if (type != LUA_TT_GENERAL) {
atomic_print(type, value, true);
LS.result();
return;
}
// Find out whether it has a table number. If necessary,
// assign one.
int tabnum = 0;
LS.rawget(lchpos, tabchpos_, value);
if (!LS.isnumber(lchpos)) {
// First time. Record the character position where the
// table first appears in the output stream.
LS.rawset(tabchpos_, value, int((*output_).tellp()));
} else {
int chpos = LS.ckint(lchpos);
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;
}
}
// 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_) << "{...}";
}
return;
}
// 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;
tabify(level + 1);
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
(*output_) << LS.ckstring(key);
} else {
(*output_) << "[";
pprint_r(level + 1, false, key);
(*output_) << "]";
}
if (indent_) {
(*output_) << " = ";
} else {
(*output_) << "=";
}
pprint_r(level + 1, false, val);
}
}
// Output the metatable.
LS.getmetatable(val, value);
if (LS.istable(val)) {
multiline = true;
if (needcomma) (*output_) << ",";
needcomma = true;
tabify(level + 1);
(*output_) << "<meta> = ";
pprint_r(level + 1, false, val);
}
// Close the brackets.
if (multiline) {
tabify(level);
} else if (LS.ckinteger(nextseq) > 1) {
(*output_) << " ";
}
(*output_) << "}";
LS.result();
}
// Atomic print interface.
PrintMachine(LuaStack &LS0, LuaSlot root, bool quote, std::ostream *os) :
LS_(LS0.state(), tabchpos_) {
output_ = os;
atomic_print(LS_.xtype(root), root, quote);
LS_.result();
}
// Pretty print interface.
PrintMachine(LuaStack &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 XX> in front of all tables that got referenced.
chpos_to_tabnum_.emplace(0x7FFFFFFF, 0);
auto iter = chpos_to_tabnum_.begin();
for (int i = 0; i < int(pre.size()); i++) {
if (i == iter->first) {
(*os) << "<table " << iter->second << ">";
iter++;
}
(*os).put(pre[i]);
}
LS_.result();
}
};
void PrettyPrintOptions::parse(LuaKeywordParser &kp) {
LuaVar option;
LuaStack LS(kp.state(), option);
if (kp.parse(option, "indent")) {
indent = LS.ckboolean(option);
}
if (kp.parse(option, "level")) {
level = LS.ckint(option);
}
if (kp.parse(option, "expand")) {
expand = LS.ckboolean(option);
}
LS.result();
}
LuaDefine(table_findtables, "root", "recursively find tables (debugging only)") {
LuaArg root;
LuaRet tabcount;
LuaStack LS(L, root, tabcount);
findtables(LS, root, tabcount);
void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
PrintMachine pm(LS, val, quote, os);
}
void pprint(LuaStack &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."
"|") {
int n = lua_gettop(L);
LuaRet result;
LuaStack LS(L, result);
util::ostringstream oss;
for (int i = 1; i <= n; i++) {
LuaSpecial root(i);
pprint(LS, root, PrettyPrintOptions(), &oss);
if (i < n) oss << "\n";
}
oss << std::endl;
LS.set(result, oss.view());
return LS.result();
}
struct Inspector {
lua_State *L;
LuaVar ids;
int nextid;
bool indent;
int maxlen;
bool anyindent;
std::ostream *stream;
};
static void tabify(Inspector &insp, int level) {
if (insp.indent) {
(*insp.stream) << std::endl;
for (int i = 0; i < level; i++) {
(*insp.stream) << " ";
}
insp.anyindent = true;
} else {
(*insp.stream) << " ";
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;
LuaStack LS(L, loptions, result, value);
PrettyPrintOptions options;
LuaKeywordParser kp(LS, loptions);
options.parse(kp);
if (!kp.parse(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();
}
static void pprint_r(Inspector &insp, int level, LuaSlot root) {
lua_checkstack(insp.L, 20);
LuaVar idv, pairs, key, val, nextseq;
LuaStack LS(insp.L, idv, pairs, key, val, nextseq);
// If it's anything but a table, use 'atomic_print'.
if (!LS.istable(root)) {
atomic_print(LS, root, true, insp.stream);
LS.result();
return;
}
// Determine the table's ID, allocating an ID if necessary.
LS.rawget(idv, insp.ids, root);
int id = LS.ckint(idv);
bool new_id = false;
if (id < 0) {
new_id = true;
id = insp.nextid++;
LS.rawset(insp.ids, root, id);
}
// Print the table's name, if any.
bool is_class = false;
bool is_tangible = false;
eng::string cname = LS.classname(root);
if (cname != "") {
is_class = true;
(*insp.stream) << "<class " << cname << ">";
} else {
int64_t tid = LS.tanid(root);
if (tid > 0) {
is_tangible = true;
(*insp.stream) << "<tangible " << tid << ">";
} else if (id > 0) {
(*insp.stream) << "<table " << id << ">";
}
}
// If this is a class, and we're not at the top level, truncate.
if ((is_class || is_tangible) && (level > 0)) {
LS.result();
return;
}
// If this is a table we've already printed, truncate it.
if ((id > 0) && (!new_id)) {
if (lua_nkeys(insp.L, root.index())==0) {
(*insp.stream) << "{}";
} else {
(*insp.stream) << "{...}";
}
LS.result();
return;
}
// State variables.
bool needcomma = false;
bool multiline = false;
LS.set(nextseq, 1);
// Open the brackets.
(*insp.stream) << "{";
// Output the table keys.
table_getpairs(LS, root, 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) (*insp.stream) << ",";
needcomma = true;
if (LS.rawequal(key, nextseq)) {
(*insp.stream) << " ";
pprint_r(insp, level + 1, val);
LS.set(nextseq, LS.ckinteger(nextseq) + 1);
} else {
multiline = true;
tabify(insp, level + 1);
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
(*insp.stream) << LS.ckstring(key);
} else {
(*insp.stream) << "[";
pprint_r(insp, level + 1, key);
(*insp.stream) << "]";
}
if (insp.indent) {
(*insp.stream) << " = ";
} else {
(*insp.stream) << "=";
}
pprint_r(insp, level + 1, val);
}
}
// Output the metatable.
LS.getmetatable(val, root);
if (LS.istable(val)) {
multiline = true;
if (needcomma) (*insp.stream) << ",";
needcomma = true;
tabify(insp, level + 1);
(*insp.stream) << "<meta> = ";
pprint_r(insp, level + 1, val);
}
// Close the brackets.
if (multiline) {
tabify(insp, level);
} else if (LS.ckinteger(nextseq) > 1) {
(*insp.stream) << " ";
}
(*insp.stream) << "}";
LS.result();
LuaDefine(string_print, "obj",
"|Concise print the specified object into a string"
"|"
"|This prints a concise representation of obj into a string. Tables"
"|are not expanded: for that, use string.pprintx. The functions"
"|tostring and string.print are identical."
"|") {
LuaArg val;
LuaRet result;
LuaStack LS(L, val, result);
eng::ostringstream oss;
atomic_print(LS, val, false, &oss);
LS.set(result, oss.str());
return LS.result();
}
void pprint(LuaStack &LS0, LuaSlot root, bool indent, std::ostream *os) {
Inspector insp;
LuaStack LS(LS0.state(), insp.ids);
findtables(LS, root, insp.ids);
insp.L = LS0.state();
insp.nextid = 1;
insp.indent = indent;
insp.anyindent = false;
insp.stream = os;
pprint_r(insp, 0, root);
LS.result();
LuaDefine(tostring, "obj",
"|Concise print the specified object into a string"
"|"
"|This prints a concise representation of obj into a string. Tables"
"|are not expanded: for that, use string.pprint. The functions"
"|tostring and string.print are identical."
"|") {
LuaArg val;
LuaRet result;
LuaStack LS(L, val, result);
eng::ostringstream oss;
atomic_print(LS, val, false, &oss);
LS.set(result, oss.str());
return LS.result();
}
LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") {
@@ -271,33 +368,5 @@ LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua
return LS.result();
}
LuaDefine(string_print, "obj", "print the specified object into a string") {
LuaArg val;
LuaRet result;
LuaStack LS(L, val, result);
eng::ostringstream oss;
atomic_print(LS, val, false, &oss);
LS.set(result, oss.str());
return LS.result();
}
LuaDefine(string_pprint, "obj,indent", "pretty-print the specified object into a string") {
LuaArg root, indent;
LuaRet result;
LuaStack LS(L, root, indent, result);
bool ind = LS.ckboolean(indent);
eng::ostringstream oss;
pprint(LS, root, ind, &oss);
LS.set(result, oss.str());
return LS.result();
}
LuaDefine(tostring, "obj", "print the specified object into a string") {
LuaArg val;
LuaRet result;
LuaStack LS(L, val, result);
eng::ostringstream oss;
atomic_print(LS, val, false, &oss);
LS.set(result, oss.str());
return LS.result();
}