2022-02-24 02:17:41 -05:00
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
#include <ostream>
|
2021-10-21 13:15:04 -04:00
|
|
|
#include "pprint.hpp"
|
2021-09-07 17:37:23 -04:00
|
|
|
#include "util.hpp"
|
2021-09-08 01:32:08 -04:00
|
|
|
#include "table.hpp"
|
2022-02-23 23:08:28 -05:00
|
|
|
|
2022-06-06 23:03:26 -04:00
|
|
|
#include <cmath>
|
2021-09-07 17:37:23 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
class PrintMachine {
|
|
|
|
|
public:
|
|
|
|
|
LuaVar tabchpos_;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaExtStack LS_;
|
2023-03-05 01:51:25 -05:00
|
|
|
int next_id_;
|
|
|
|
|
bool indent_;
|
|
|
|
|
std::ostream *output_;
|
|
|
|
|
eng::map<int, int> chpos_to_tabnum_;
|
2021-09-07 17:37:23 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
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_);
|
2022-06-06 23:03:26 -04:00
|
|
|
} else {
|
2023-03-05 01:51:25 -05:00
|
|
|
// 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;
|
|
|
|
|
}
|
2022-06-06 23:03:26 -04:00
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
return;
|
2021-12-14 10:51:00 -05:00
|
|
|
}
|
2023-03-05 01:51:25 -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: {
|
|
|
|
|
(*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;
|
|
|
|
|
}
|
2023-03-16 23:31:29 -04:00
|
|
|
case LUA_TT_TANGIBLEMETA: {
|
|
|
|
|
(*output_) << "<tangible-metatable>";
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
default: {
|
|
|
|
|
(*output_) << "<unknown type #" << xtype << ">";
|
|
|
|
|
return;
|
|
|
|
|
}}
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
2021-09-07 17:37:23 -04:00
|
|
|
|
2023-03-05 01:51:25 -05: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 {
|
2023-03-05 01:51:25 -05:00
|
|
|
(*output_) << " ";
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-05 01:51:25 -05: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, nextseq;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaExtStack LS(L, loffset, pairs, key, val, lchpos, nextseq);
|
2021-09-07 17:37:23 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Determine the extended type of the object. If the
|
|
|
|
|
// expand flag is true, try to coerce it to a general table.
|
2023-03-16 23:31:29 -04:00
|
|
|
int xtype = LS.xtype(value);
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-16 23:31:29 -04:00
|
|
|
// Print the atomic portion.
|
|
|
|
|
if (xtype != LUA_TT_GENERAL) {
|
|
|
|
|
atomic_print(xtype, value, true);
|
|
|
|
|
if ((xtype < LUA_TT_GENERAL) || (!expand)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Find out whether it has a table number. If necessary,
|
|
|
|
|
// assign one.
|
|
|
|
|
int tabnum = 0;
|
|
|
|
|
LS.rawget(lchpos, tabchpos_, value);
|
2024-03-15 12:50:08 -04:00
|
|
|
auto chpos = LS.tryint(lchpos);
|
|
|
|
|
if (!chpos) {
|
2023-03-05 01:51:25 -05:00
|
|
|
// First time. Record the character position where the
|
|
|
|
|
// table first appears in the output stream.
|
|
|
|
|
LS.rawset(tabchpos_, value, int((*output_).tellp()));
|
|
|
|
|
} else {
|
2024-03-15 12:50:08 -04:00
|
|
|
tabnum = chpos_to_tabnum_[*chpos];
|
2023-03-05 01:51:25 -05:00
|
|
|
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_++;
|
2024-03-15 12:50:08 -04:00
|
|
|
chpos_to_tabnum_[*chpos] = tabnum;
|
2023-03-05 01:51:25 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// State variables.
|
|
|
|
|
bool needcomma = false;
|
|
|
|
|
bool multiline = false;
|
|
|
|
|
LS.set(nextseq, 1);
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Open the brackets.
|
|
|
|
|
(*output_) << "{";
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// 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);
|
2025-01-20 21:11:59 -05:00
|
|
|
} else if (LS.istoken(key)) {
|
|
|
|
|
atomic_print(LUA_TLIGHTUSERDATA, key, false);
|
2023-03-05 01:51:25 -05:00
|
|
|
} else {
|
|
|
|
|
(*output_) << "[";
|
|
|
|
|
pprint_r(level + 1, false, key);
|
|
|
|
|
(*output_) << "]";
|
|
|
|
|
}
|
|
|
|
|
if (indent_) {
|
|
|
|
|
(*output_) << " = ";
|
|
|
|
|
} else {
|
|
|
|
|
(*output_) << "=";
|
|
|
|
|
}
|
|
|
|
|
pprint_r(level + 1, false, val);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-26 12:28:59 -05:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Output the metatable.
|
|
|
|
|
LS.getmetatable(val, value);
|
2023-03-16 23:31:29 -04:00
|
|
|
if (LS.istable(val) && (LS.gettabletype(val) != LUA_TT_TANGIBLEMETA)) {
|
2023-03-05 01:51:25 -05:00
|
|
|
multiline = true;
|
|
|
|
|
if (needcomma) (*output_) << ",";
|
|
|
|
|
needcomma = true;
|
|
|
|
|
tabify(level + 1);
|
|
|
|
|
(*output_) << "<meta> = ";
|
|
|
|
|
pprint_r(level + 1, false, val);
|
2021-11-26 12:28:59 -05:00
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
|
|
|
|
|
// Close the brackets.
|
|
|
|
|
if (multiline) {
|
|
|
|
|
tabify(level);
|
|
|
|
|
} else if (LS.ckinteger(nextseq) > 1) {
|
|
|
|
|
(*output_) << " ";
|
|
|
|
|
}
|
|
|
|
|
(*output_) << "}";
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Atomic print interface.
|
2023-04-06 20:12:03 -04:00
|
|
|
PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool quote, std::ostream *os) :
|
2023-03-05 01:51:25 -05:00
|
|
|
LS_(LS0.state(), tabchpos_) {
|
|
|
|
|
output_ = os;
|
|
|
|
|
atomic_print(LS_.xtype(root), root, quote);
|
|
|
|
|
}
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
// Pretty print interface.
|
2023-04-06 20:12:03 -04:00
|
|
|
PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool indent, int level, bool expand, std::ostream *os) :
|
2023-03-05 01:51:25 -05:00
|
|
|
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++;
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
(*os).put(pre[i]);
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
};
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
void PrettyPrintOptions::parse(LuaKeywordParser &kp) {
|
|
|
|
|
LuaVar option;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaExtStack LS(kp.state(), option);
|
2025-01-20 18:54:05 -05:00
|
|
|
kp.check_throw();
|
|
|
|
|
if (kp.optional(option, "indent")) {
|
2023-03-05 01:51:25 -05:00
|
|
|
indent = LS.ckboolean(option);
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
2025-01-20 18:54:05 -05:00
|
|
|
if (kp.optional(option, "level")) {
|
2023-03-05 01:51:25 -05:00
|
|
|
level = LS.ckint(option);
|
|
|
|
|
}
|
2025-01-20 18:54:05 -05:00
|
|
|
if (kp.optional(option, "expand")) {
|
2023-03-05 01:51:25 -05:00
|
|
|
expand = LS.ckboolean(option);
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-06 20:12:03 -04:00
|
|
|
void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
2023-03-05 01:51:25 -05:00
|
|
|
PrintMachine pm(LS, val, quote, os);
|
2021-09-08 01:32:08 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-06 20:12:03 -04:00
|
|
|
void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os) {
|
2023-03-05 01:51:25 -05:00
|
|
|
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);
|
|
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
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";
|
2021-10-21 14:22:06 -04:00
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
LS.set(result, oss.view());
|
2023-04-13 13:26:45 -04:00
|
|
|
return LS.result();
|
2021-10-21 14:22:06 -04:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
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;
|
2021-10-21 14:22:06 -04:00
|
|
|
LuaRet result;
|
2023-03-05 01:51:25 -05:00
|
|
|
LuaVar value;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaDefStack LS(L, loptions, result, value);
|
2023-03-05 01:51:25 -05:00
|
|
|
PrettyPrintOptions options;
|
|
|
|
|
LuaKeywordParser kp(LS, loptions);
|
|
|
|
|
options.parse(kp);
|
2025-01-20 18:54:05 -05:00
|
|
|
if (!kp.optional(value, "value")) {
|
2023-03-05 01:51:25 -05:00
|
|
|
LS.set(value, LuaNil);
|
|
|
|
|
}
|
|
|
|
|
kp.final_check_throw();
|
|
|
|
|
util::ostringstream oss;
|
|
|
|
|
pprint(LS, value, options, &oss);
|
|
|
|
|
LS.set(result, oss.view());
|
2021-10-21 14:22:06 -04:00
|
|
|
return LS.result();
|
|
|
|
|
}
|
2021-09-08 01:32:08 -04:00
|
|
|
|
2023-03-05 01:51:25 -05: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-03-05 01:51:25 -05:00
|
|
|
"|"
|
2023-04-14 14:52:44 -04:00
|
|
|
"|The functions string.print and tostring are identical."
|
2023-03-05 01:51:25 -05:00
|
|
|
"|") {
|
2021-10-21 14:22:06 -04:00
|
|
|
LuaArg val;
|
|
|
|
|
LuaRet result;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaDefStack LS(L, val, result);
|
2022-02-24 02:17:41 -05:00
|
|
|
eng::ostringstream oss;
|
2021-12-15 23:03:43 -05:00
|
|
|
atomic_print(LS, val, false, &oss);
|
2021-10-21 14:22:06 -04:00
|
|
|
LS.set(result, oss.str());
|
|
|
|
|
return LS.result();
|
|
|
|
|
}
|
2023-03-05 01:51:25 -05:00
|
|
|
|
2023-04-14 14:52:44 -04:00
|
|
|
LuaDefineAlias(tostring, string_print);
|
|
|
|
|
|
2023-03-05 01:51:25 -05:00
|
|
|
LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") {
|
|
|
|
|
LuaArg str;
|
|
|
|
|
LuaRet result;
|
2023-04-07 14:20:45 -04:00
|
|
|
LuaDefStack LS(L, str, result);
|
2023-03-05 01:51:25 -05:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|