From 0de2a5084373026ac1840daf941f0b6a23d63960 Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 18 Feb 2026 22:26:11 -0500 Subject: [PATCH] A lot of refactoring on the PrettyPrint code and the various printf routines. --- luprex/cpp/core/pprint.cpp | 167 +++++++++++++++-------------- luprex/cpp/core/pprint.hpp | 117 +++++++++++--------- luprex/cpp/core/world-accessor.cpp | 10 +- luprex/cpp/core/world-core.cpp | 4 +- luprex/cpp/core/world-testing.cpp | 4 +- 5 files changed, 160 insertions(+), 142 deletions(-) diff --git a/luprex/cpp/core/pprint.cpp b/luprex/cpp/core/pprint.cpp index b31926b4..792ae356 100644 --- a/luprex/cpp/core/pprint.cpp +++ b/luprex/cpp/core/pprint.cpp @@ -251,24 +251,21 @@ public: } } - // 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); - } - - // Pretty print interface. - PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool indent, int level, bool expand, std::ostream *os) : + PrintMachine(LuaCoreStack &LS0, LuaSlot root, const PrettyPrint &opts, std::ostream *os) : LS_(LS0.state(), tabchpos_) { + if (opts.atomic_) { + output_ = os; + atomic_print(LS_.xtype(root), root, opts.quote_); + return; + } next_id_ = 1; - indent_ = indent; + indent_ = opts.indent_; LS_.newtable(tabchpos_); util::ostringstream preoutput; output_ = &preoutput; - pprint_r(level, expand, root); + pprint_r(opts.level_, opts.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 @@ -285,27 +282,9 @@ public: } }; -void PrettyPrintOptions::parse(LuaKeywordParser &kp) { - LuaVar option; - LuaExtStack LS(kp.state(), option); - kp.check_throw(); - if (kp.optional(option, "indent")) { - indent = LS.ckboolean(option); - } - if (kp.optional(option, "level")) { - level = LS.ckint(option); - } - if (kp.optional(option, "expand")) { - expand = LS.ckboolean(option); - } -} -void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os) { - PrintMachine pm(LS, val, quote, os); -} - -void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os) { - PrintMachine pm(LS, val, opts.indent, opts.level, opts.expand, os); +void PrettyPrint::print(LuaCoreStack &LS, LuaSlot val, std::ostream *os) const { + PrintMachine pm(LS, val, *this, os); } @@ -319,39 +298,22 @@ void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std:: class FormatDirective { - // Given a string_view that starts with '%', count the number of characters - // in the format parameters: the '%', flags, width, and precision. Does NOT - // include the conversion character. - // - // For example, given "%8.2d %2.7d", returns 4 (the length of "%8.2"). - // - static int format_parameters_length(std::string_view fmt) { - assert ((fmt.size() >= 1) && (fmt[0] == '%')); - size_t i = 1; - - // Flags - while (i < fmt.size() && (fmt[i] == '-' || fmt[i] == '+' || fmt[i] == ' ' || fmt[i] == '#' || fmt[i] == '0')) - i++; - - // Width - while (i < fmt.size() && fmt[i] >= '0' && fmt[i] <= '9') - i++; - - // Precision - if (i < fmt.size() && fmt[i] == '.') { - i++; - while (i < fmt.size() && fmt[i] >= '0' && fmt[i] <= '9') - i++; - } - return (int)i; - } - public: std::string_view precedingliteral; - std::string_view parameters; + std::string_view parameters; // Raw string_view of '%', flags, width, precision. std::string_view modifiers; char directive; char rebuilt[100]; + + // Parsed fields from parameters. + bool flag_minus; + bool flag_plus; + bool flag_space; + bool flag_hash; + bool flag_zero; + int width; // -1 if unspecified. + int precision; // -1 if unspecified. + const int PARAMETERS_TOO_LONG = 50; // Return an error message declaring this format specifier to be invalid. @@ -386,8 +348,8 @@ public: { // Find the preceding literal (everything before the first '%'). size_t pct = fmt.find('%'); + precedingliteral = fmt.substr(0, pct); if (pct == std::string_view::npos) { - precedingliteral = fmt; parameters = {}; modifiers = {}; directive = 0; @@ -395,25 +357,52 @@ public: return; } - precedingliteral = fmt.substr(0, pct); - fmt.remove_prefix(pct); + // Parse format parameters: '%', flags, width, precision. + // We parse into individual fields AND store the raw string_view. + std::string_view rest = fmt.substr(pct + 1); - // Measure format parameters (%, flags, width, precision). - int plen = format_parameters_length(fmt); - parameters = fmt.substr(0, plen); - fmt.remove_prefix(plen); + // Flags. + flag_minus = false; + flag_plus = false; + flag_space = false; + flag_hash = false; + flag_zero = false; + for (;;) { + switch (sv::zfront(rest)) { + case '-': flag_minus = true; rest.remove_prefix(1); continue; + case '+': flag_plus = true; rest.remove_prefix(1); continue; + case ' ': flag_space = true; rest.remove_prefix(1); continue; + case '#': flag_hash = true; rest.remove_prefix(1); continue; + case '0': flag_zero = true; rest.remove_prefix(1); continue; + default: break; + } + break; + } + + // Width. + std::string_view width_str = sv::read_number(rest, false, false, false, false); + width = width_str.empty() ? -1 : (int)sv::to_int64(width_str); + + // Precision. + precision = -1; + if (sv::zfront(rest) == '.') { + rest.remove_prefix(1); + std::string_view prec_str = sv::read_number(rest, false, false, false, false); + precision = prec_str.empty() ? 0 : (int)sv::to_int64(prec_str); + } + + parameters = fmt.substr(pct, fmt.size() - pct - rest.size()); + fmt = rest; // Read 'l' modifiers. - int modcount = 0; - while ((modcount < int(fmt.size())) && (fmt[modcount] == 'l')) - modcount++; - modifiers = fmt.substr(0, modcount); - fmt.remove_prefix(modcount); + std::string_view mod_start = fmt; + while (sv::zfront(fmt) == 'l') + fmt.remove_prefix(1); + modifiers = mod_start.substr(0, mod_start.size() - fmt.size()); // Read conversion character. - if (fmt.empty()) { directive = 0; return; } - directive = fmt[0]; - fmt.remove_prefix(1); + directive = sv::zfront(fmt); + if (directive) fmt.remove_prefix(1); } }; @@ -436,6 +425,22 @@ static void format_unsigned(LuaCoreStack &LS, LuaSlot arg, const char *format, s } } +static void format_value(LuaCoreStack &LS, LuaSlot arg, FormatDirective &fd, const PrettyPrint &opts, std::ostream *os) { + if (fd.width <= 0) { + PrintMachine pm(LS, arg, opts, os); + return; + } + eng::ostringstream tmp; + PrintMachine pm(LS, arg, opts, &tmp); + std::string_view sv = tmp.view(); + int pad = fd.width - (int)sv.size(); + if (!fd.flag_minus) + for (int i = 0; i < pad; i++) os->put(' '); + os->write(sv.data(), sv.size()); + if (fd.flag_minus) + for (int i = 0; i < pad; i++) os->put(' '); +} + static void format_double(LuaCoreStack &LS, LuaSlot arg, const char *format, std::ostream *os) { auto ni = LS.trynumber(arg); if (ni) { @@ -446,7 +451,7 @@ static void format_double(LuaCoreStack &LS, LuaSlot arg, const char *format, std } -eng::string format(LuaCoreStack &LS, std::string_view fmt, LuaExtraArgs args, std::ostream *os) { +eng::string PrettyPrint::format(LuaCoreStack &LS, std::string_view fmt, LuaExtraArgs args, std::ostream *os) { FormatDirective fd; // First pass: validate the format string and the argument types. @@ -513,10 +518,10 @@ eng::string format(LuaCoreStack &LS, std::string_view fmt, LuaExtraArgs args, st case 'g': format_double(LS, arg, fd.rebuild("g"), os); break; case 'G': format_double(LS, arg, fd.rebuild("G"), os); break; case 'c': format_signed(LS, arg, fd.rebuild("c"), os); break; - case 's': atomic_print(LS, arg, false, os); break; - case 'q': atomic_print(LS, arg, true, os); break; - case 'p': pprint(LS, arg, PrettyPrintOptions(fd.modifiers.empty(), false), os); break; - case 'P': pprint(LS, arg, PrettyPrintOptions(fd.modifiers.empty(), true), os); break; + case 's': format_value(LS, arg, fd, PrettyPrint::Atomic(false), os); break; + case 'q': format_value(LS, arg, fd, PrettyPrint::Atomic(true), os); break; + case 'p': format_value(LS, arg, fd, PrettyPrint::Pretty(fd.modifiers.empty(), false), os); break; + case 'P': format_value(LS, arg, fd, PrettyPrint::Pretty(fd.modifiers.empty(), true), os); break; default: break; } } @@ -544,7 +549,7 @@ LuaDefine(tostring, "obj", LuaRet result; LuaDefStack LS(L, val, result); eng::ostringstream oss; - atomic_print(LS, val, false, &oss); + PrettyPrint::Atomic(false).print(LS, val, &oss); LS.set(result, oss.str()); return LS.result(); } diff --git a/luprex/cpp/core/pprint.hpp b/luprex/cpp/core/pprint.hpp index e57f0d06..21eee277 100644 --- a/luprex/cpp/core/pprint.hpp +++ b/luprex/cpp/core/pprint.hpp @@ -22,57 +22,72 @@ #include "luastack.hpp" #include -struct PrettyPrintOptions { - bool indent; - int level; - bool expand; - PrettyPrintOptions() : indent(true), level(0), expand(true) {} - PrettyPrintOptions(bool indent, bool expand) : indent(indent), level(0), expand(expand) {} - void parse(LuaKeywordParser &kp); +class PrettyPrint { + bool atomic_; + bool quote_; + bool indent_; + int level_; + bool expand_; + PrettyPrint(bool atomic, bool quote, bool indent, int level, bool expand) + : atomic_(atomic), quote_(quote), indent_(indent), level_(level), expand_(expand) {} +public: + // PrettyPrint that pretty-prints and indents and + // expands top-level tables. This is probably the + // default you want for most things. + // + static PrettyPrint Indented() { return {false, false, true, 0, true}; } + + // PrettyPrint that pretty-prints, you can specify + // whether it indents and whether it expands top-level + // tables. + // + static PrettyPrint Pretty(bool indent, bool expand) { return {false, false, indent, 0, expand}; } + + // PrettyPrint that prints something atomically, not + // showing the contents of tables. You can specify + // whether strings are to be quoted or not. + // + static PrettyPrint Atomic(bool quote) { return {true, quote, true, 0, true}; } + + // Print a single value in the specified style. + // + void print(LuaCoreStack &LS, LuaSlot val, std::ostream *os) const; + + // Format a string using printf-style format specifiers, consuming + // arguments from a LuaExtraArgs. Returns an empty string on success, + // or an error message on failure. Errors mainly consist of + // invalid format strings or incorrect arguments for the format + // string. + // + // Numeric types (argument must be a number). All of the following + // use the same formatting parameters as 'printf'. + // + // %d, %i — signed decimal integer + // %o — unsigned octal + // %u — unsigned decimal + // %x, %X — unsigned hexadecimal (lower/upper) + // %e, %E — scientific notation (lower/upper) + // %f — decimal floating point + // %g, %G — shortest of %e/%f (lower/upper) + // %c — character (integer converted to character) + // + // Non-numeric types: + // %s — prints any lua value using atomic_print (unquoted) + // %q — prints any lua value using atomic_print (quoted) + // + // Pretty-printing: + // + // %p - pretty print + // %lp - pretty print, but all on one line + // %P - pretty print, force table expansion + // %lP - pretty print, force table expansion, all on one line + // + // Special: + // %% — literal percent sign (consumes no argument) + // + static eng::string format(LuaCoreStack &LS, std::string_view fmt, LuaExtraArgs args, std::ostream *os); + + friend class PrintMachine; }; -// Atomic print to a stream. -// -// This prints an atomic value to a stream. If you give it a table, -// it just prints "". This routine is the heart of the lua -// primitives 'print' and 'tostring'. -// -void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os); - -// Pretty print to a stream. -// -void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os); - -// Format a string using printf-style format specifiers, consuming -// arguments from a LuaExtraArgs. Returns an empty string on success, -// or an error message on failure. -// -// Numeric types (argument must be a number). All of the following -// use the same formatting parameters as 'printf'. -// -// %d, %i — signed decimal integer -// %o — unsigned octal -// %u — unsigned decimal -// %x, %X — unsigned hexadecimal (lower/upper) -// %e, %E — scientific notation (lower/upper) -// %f — decimal floating point -// %g, %G — shortest of %e/%f (lower/upper) -// %c — character (integer converted to character) -// -// Non-numeric types: -// %s — prints any lua value using atomic_print (unquoted) -// %q — prints any lua value using atomic_print (quoted) -// -// Pretty-printing: -// -// %p - pretty print -// %lp - pretty print, but all on one line -// %P - pretty print, force table expansion -// %lP - pretty print, force table expansion, all on one line -// -// Special: -// %% — literal percent sign (consumes no argument) -// -eng::string format(LuaCoreStack &LS, std::string_view fmt, LuaExtraArgs args, std::ostream *os); - #endif // PPRINT_HPP \ No newline at end of file diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index d0f62a9d..c5d99085 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -865,7 +865,7 @@ LuaDefine(print, "obj1, obj2, ...", int n = lua_gettop(L); for (int i = 1; i <= n; i++) { LuaSpecial root(i); - atomic_print(LS, root, false, ostream); + PrettyPrint::Atomic(false).print(LS, root, ostream); if (i < n) (*ostream) << " "; } (*ostream) << std::endl; @@ -879,7 +879,7 @@ LuaDefine(dprint, "obj1, obj2, ...", int n = lua_gettop(L); for (int i = 1; i <= n; i++) { LuaSpecial root(i); - atomic_print(LS, root, false, &oss); + PrettyPrint::Atomic(false).print(LS, root, &oss); if (i < n) oss << " "; } oss << std::endl; @@ -925,7 +925,7 @@ LuaDefine(printf, "fmt, ...", LuaExtraArgs extra; LuaDefStack LS(L, lfmt, extra); eng::string fmtstr = LS.ckstring(lfmt, "format string"); - eng::string err = format(LS, fmtstr, extra, ostream); + eng::string err = PrettyPrint::format(LS, fmtstr, extra, ostream); if (!err.empty()) { luaL_error(L, "%s", err.c_str()); } @@ -942,7 +942,7 @@ LuaDefine(dprintf, "fmt, ...", LuaDefStack LS(L, lfmt, extra); eng::string fmtstr = LS.ckstring(lfmt, "format string"); std::ostringstream oss; - eng::string err = format(LS, fmtstr, extra, &oss); + eng::string err = PrettyPrint::format(LS, fmtstr, extra, &oss); if (!err.empty()) { luaL_error(L, "%s", err.c_str()); } @@ -961,7 +961,7 @@ LuaDefine(string_format, "fmt, ...", LuaDefStack LS(L, lfmt, result, extra); eng::string fmtstr = LS.ckstring(lfmt, "format string"); util::ostringstream oss; - eng::string err = format(LS, fmtstr, extra, &oss); + eng::string err = PrettyPrint::format(LS, fmtstr, extra, &oss); if (!err.empty()) { luaL_error(L, "%s", err.c_str()); } diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 8b02d272..0b8052e2 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -395,7 +395,7 @@ eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) { if (msg.empty()) { for (int i = top + 1; i <= lua_gettop(L); i++) { LuaSpecial root(i); - pprint(LS, root, PrettyPrintOptions(), <hread_prints_); + PrettyPrint::Indented().print(LS, root, <hread_prints_); // TODO: this endl is unnecessary if we just printed a newline. lthread_prints_ << std::endl; } @@ -1076,7 +1076,7 @@ void World::run_scheduled_threads() { LuaCoreStack LSCO(CO); if (LS.ckboolean(print)) { for (int i = 1; i <= lua_gettop(CO); i++) { - pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), <hread_prints_); + PrettyPrint::Indented().print(LSCO, LuaSpecial(i), <hread_prints_); lthread_prints_ << std::endl; } } diff --git a/luprex/cpp/core/world-testing.cpp b/luprex/cpp/core/world-testing.cpp index a9fcc4aa..62d3907b 100644 --- a/luprex/cpp/core/world-testing.cpp +++ b/luprex/cpp/core/world-testing.cpp @@ -85,9 +85,7 @@ eng::string World::tangible_pprint(int64_t id) const { LS.rawget(tan, tangibles, id); eng::ostringstream oss; if (LS.istable(tan)) { - PrettyPrintOptions opts; - opts.indent = false; - pprint(LS, tan, opts, &oss); + PrettyPrint::Pretty(false, true).print(LS, tan, &oss); } else { oss << "{}"; }