pretty printing -- copmlete for xo::ast::GeneralizedExpression

This commit is contained in:
Roland Conybeare 2025-07-19 11:47:03 -05:00
commit c2dcc84c2e
14 changed files with 572 additions and 292 deletions

View file

@ -4,8 +4,23 @@ Glossary
-------- --------
.. glossary:: .. glossary::
iff
| Short for "if and only if"
pp pp
| Short for `pretty printer`. For example in `ppdetail`, `toppstr`. | Short for "pretty printer". For example in `ppdetail`, `toppstr`.
ppii
| Short for "pretty-print indent info"
pps
| Short for "pretty-print state"
rtag
| Shorthand for "raw tag"
xtag xtag
| Shorthand for `tag with preceding space` | Shorthand for "tag with preceding space"
xrtag
| Shorthand for "raw tag with preceding space"

View file

@ -16,6 +16,16 @@ namespace xo {
**/ **/
template <typename CharT, typename Traits = std::char_traits<CharT>> template <typename CharT, typename Traits = std::char_traits<CharT>>
class log_streambuf : public std::streambuf { class log_streambuf : public std::streambuf {
public:
struct rewind_state {
explicit rewind_state(std::size_t solpos, std::size_t color_esc, std::uint32_t p)
: solpos{solpos}, color_escape_chars{color_esc}, pos{p} {}
std::size_t solpos = 0;
std::size_t color_escape_chars = 0;
std::uint32_t pos = 0;
};
public: public:
log_streambuf(std::uint32_t buf_z, bool debug_flag = false) : debug_flag_{debug_flag} { log_streambuf(std::uint32_t buf_z, bool debug_flag = false) : debug_flag_{debug_flag} {
this->buf_v_.resize(buf_z); this->buf_v_.resize(buf_z);
@ -27,8 +37,15 @@ namespace xo {
char const * hi() const { return this->lo() + this->capacity(); } char const * hi() const { return this->lo() + this->capacity(); }
std::uint32_t pos() const { return this->pptr() - this->pbase(); } std::uint32_t pos() const { return this->pptr() - this->pbase(); }
/** number of characters since start of line (last \n or \r) **/ /** number of visible characters since start of line (last \n or \r) **/
std::uint32_t lpos() const { return pos() - solpos_; } std::uint32_t lpos() const {
assert(pos() >= solpos_ + color_escape_chars_);
return pos() - solpos_ - color_escape_chars_;
}
rewind_state checkpoint() const {
return rewind_state(solpos_, color_escape_chars_, pos());
}
bool debug_flag() const { return debug_flag_; } bool debug_flag() const { return debug_flag_; }
@ -40,12 +57,24 @@ namespace xo {
this->setp(p_lo, p_hi); this->setp(p_lo, p_hi);
this->solpos_ = 0; this->solpos_ = 0;
this->color_escape_chars_ = 0;
this->color_escape_start_ = nullptr;
} /*reset_stream*/ } /*reset_stream*/
void rewind_to(std::uint32_t p) { void rewind_to(rewind_state s) {
if (debug_flag_) {
std::cout << "rewind_to: pos " << pos() << "->" << s.pos
<< " solpos " << solpos_ << "->" << s.solpos
<< " color_esc " << color_escape_chars_ << "->" << s.color_escape_chars
<< std::endl;
}
/* .setp(): using for side effect: sets .pptr to .pbase */ /* .setp(): using for side effect: sets .pptr to .pbase */
this->setp(this->pbase(), this->epptr()); this->setp(this->pbase(), this->epptr());
this->pbump(p); this->pbump(s.pos);
this->solpos_ = s.solpos;
this->color_escape_chars_ = s.color_escape_chars;
} }
protected: protected:
@ -65,7 +94,7 @@ namespace xo {
this->setp(p_base, p_hi); this->setp(p_base, p_hi);
this->pbump(old_n); this->pbump(old_n);
} /*expand*/ }
virtual std::streamsize virtual std::streamsize
xsputn(char const * s, std::streamsize n) override { xsputn(char const * s, std::streamsize n) override {
@ -94,7 +123,7 @@ namespace xo {
ncopied = n; ncopied = n;
} }
if (debug_flag_) { if (false /*debug_flag_*/) {
std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)" std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)"
<< ", lo=" << (void*)this->pptr() << ", lo=" << (void*)this->pptr()
<< ", hi=" << (void*)(this->pptr() + n) << ", hi=" << (void*)(this->pptr() + n)
@ -103,11 +132,39 @@ namespace xo {
std::memcpy(this->pptr(), s, ncopied); std::memcpy(this->pptr(), s, ncopied);
/* scan range [pptr, pptr+n] backwards, to account for newline (if any) */ /* scan range [pptr, pptr+n] for:
for (char const * p_lo = this->pptr(), * p_hi = p_lo + n - 1, * p = p_hi; p >= p_lo; --p) { * 1. completed color escape sequences \033..m
* - account for chars in these sequences, since non-printing
* 2. newlines (and carriage returns)
* - remember position of last {newline or carriage return)
*/
for (char const * p_lo = this->pptr(), * p_hi = p_lo + n, * p = p_lo; p < p_hi; ++p) {
if (*p == '\n' || *p == '\r') { if (*p == '\n' || *p == '\r') {
this->solpos_ = (p+1 - this->pbase()); this->solpos_ = (p+1 - this->pbase());
break; /* reset, since these chars relevant as correction to solpos */
this->color_escape_chars_ = 0;
/* -> incomplete color escape, broken by newline */
this->color_escape_start_ = nullptr;
} else if (*p == '\033') {
if (debug_flag_) {
std::cout << "xsputn: \\033 at p-p_lo=" << (p - p_lo) << std::endl;
}
this->color_escape_start_ = p;
} else if (this->color_escape_start_ != nullptr) {
if (*p == 'm') {
/* escape seq non-printing including both endpoints */
std::int64_t esc_chars = (p+1 - color_escape_start_);
this->color_escape_chars_ += esc_chars;
if (debug_flag_) {
std::cout << "xsputn: m at p-p_lo" << (p - p_lo) << " +" << esc_chars
<< " -> color_escape_chars=" << color_escape_chars_ << std::endl;
}
this->color_escape_start_ = nullptr;
} else if (!isdigit(*p) && (*p != '[') && (*p != ';')) {
/* not color escape after all */
this->color_escape_start_ = nullptr;
}
} }
} }
@ -117,32 +174,32 @@ namespace xo {
} /*xsputn*/ } /*xsputn*/
virtual int_type virtual int_type
overflow(int_type new_ch) override overflow(int_type new_ch) override {
{ char * old_pptr = this->pptr();
char * old_pptr = this->pptr(); std::streamsize old_n = old_pptr - this->pbase();
std::streamsize old_n = old_pptr - this->pbase();
assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size())); assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size()));
if (debug_flag_) { if (debug_flag_) {
std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl; std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl;
} }
this->expand_to(2 * buf_v_.size()); this->expand_to(2 * buf_v_.size());
this->buf_v_[old_n] = new_ch; this->buf_v_[old_n] = new_ch;
this->pbump(1); this->pbump(1);
if ((new_ch == static_cast<int_type>('\n')) || (new_ch == static_cast<int_type>('\r'))) if ((new_ch == static_cast<int_type>('\n')) || (new_ch == static_cast<int_type>('\r'))) {
this->solpos_ = this->pos(); this->solpos_ = this->pos();
}
if (new_ch == Traits::eof()) { if (new_ch == Traits::eof()) {
/* reminder: returning eof sets badbit on ostream */ /* reminder: returning eof sets badbit on ostream */
return Traits::not_eof(new_ch); return Traits::not_eof(new_ch);
} else { } else {
return new_ch; return new_ch;
} }
} /*overflow*/ } /*overflow*/
/* off. offset, relative to starting point dir. /* off. offset, relative to starting point dir.
* dir. * dir.
@ -154,6 +211,9 @@ namespace xo {
std::ios_base::seekdir dir, std::ios_base::seekdir dir,
std::ios_base::openmode which) override { std::ios_base::openmode which) override {
//std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl; //std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl;
if (debug_flag_) {
std::cout << "seekoff(off,dir,which)" << std::endl;
}
// Only output stream is supported // Only output stream is supported
if (which != std::ios_base::out) if (which != std::ios_base::out)
@ -179,6 +239,13 @@ namespace xo {
* Use to drive @ref lpos * Use to drive @ref lpos
**/ **/
std::size_t solpos_ = 0; std::size_t solpos_ = 0;
/** number of non-printing chars after @ref solpos_, from
* completed color escape sequences.
* (ansi color escapes = text between '\033' and 'm')
**/
std::size_t color_escape_chars_ = 0;
/** non-null: start of incomplete color escape sequence **/
char const * color_escape_start_ = nullptr;
/** buffered output stored here **/ /** buffered output stored here **/
std::vector<char> buf_v_; std::vector<char> buf_v_;
/** true to debug log_streambuf itself **/ /** true to debug log_streambuf itself **/

View file

@ -2,8 +2,8 @@
#pragma once #pragma once
#include "ppdetail_atomic.hpp"
#include <ostream> #include <ostream>
//#include <utility> // for std::move
#include <cstdint> #include <cstdint>
namespace xo { namespace xo {
@ -17,11 +17,11 @@ namespace xo {
inline std::ostream & inline std::ostream &
operator<< (std::ostream & os, color_encoding x) { operator<< (std::ostream & os, color_encoding x) {
switch(x) { switch(x) {
case color_encoding::none: os << "none"; break; case color_encoding::none: os << "none"; break;
case color_encoding::ansi: os << "ansi"; break; case color_encoding::ansi: os << "ansi"; break;
case color_encoding::xterm: os << "xterm"; break; case color_encoding::xterm: os << "xterm"; break;
case color_encoding::rgb: os << "rgb"; break; case color_encoding::rgb: os << "rgb"; break;
default: os << "???"; break; default: os << "???"; break;
} }
return os; return os;
} /*operator<<*/ } /*operator<<*/
@ -125,11 +125,11 @@ namespace xo {
} /*operator<<*/ } /*operator<<*/
enum class coloring_control_flags : std::uint8_t { enum class coloring_control_flags : std::uint8_t {
none = 0x0, none = 0x0,
color_on = 0x01, color_on = 0x01,
contents = 0x02, contents = 0x02,
color_off = 0x04, color_off = 0x04,
all = 0x07 all = 0x07
}; };
inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) { inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) {
@ -177,25 +177,42 @@ namespace xo {
template <typename Contents> template <typename Contents>
color_impl<Contents> with_color(color_spec_type const & spec, Contents && contents) { color_impl<Contents> with_color(color_spec_type const & spec, Contents && contents) {
return color_impl<Contents>(coloring_control_flags::all, spec, std::forward<Contents>(contents)); return color_impl<Contents>(coloring_control_flags::all, spec, std::forward<Contents>(contents));
} /*with_color*/ }
inline color_impl<int> inline color_impl<int>
color_on(color_spec_type const & spec) { color_on(color_spec_type const & spec) {
return color_impl<int>(coloring_control_flags::color_on, spec, 0); return color_impl<int>(coloring_control_flags::color_on, spec, 0);
} /*color_on*/ }
inline color_impl<int> inline color_impl<int>
color_off(color_spec_type const & spec) { color_off(color_spec_type const & spec) {
/* any spec other than color_spec_type::none() works here */ /* any spec other than color_spec_type::none() works here */
return color_impl<int>(coloring_control_flags::color_off, spec, 0); return color_impl<int>(coloring_control_flags::color_off, spec, 0);
} /*color_off*/ }
template <typename Contents> template <typename Contents>
inline std::ostream & inline std::ostream &
operator<<(std::ostream & os, color_impl<Contents> const & x) { operator<<(std::ostream & os, color_impl<Contents> const & x) {
x.print(os); x.print(os);
return os; return os;
} /*operator<<*/ }
#ifndef ppdetail_atomic
namespace print {
/* concat expected to be used on short string-like things.
* i.e. don't want structure visible to pretty-printer.
* could be using it like concat("boeing", 777)
*/
template <typename Contents>
struct ppdetail<color_impl<Contents>> {
using target_type = color_impl<Contents>;
static bool print_pretty(const ppindentinfo & ppii, const target_type & x) {
return ppdetail_atomic<target_type>::print_pretty(ppii, x);
}
};
}
#endif
} /*namespace xo*/ } /*namespace xo*/

View file

@ -48,11 +48,8 @@ namespace xo {
struct ppdetail<concat_impl<T1,T2>> { struct ppdetail<concat_impl<T1,T2>> {
using target_type = concat_impl<T1,T2>; using target_type = concat_impl<T1,T2>;
static bool print_upto(ppstate * pps, const target_type & x) { static bool print_pretty(const ppindentinfo & ppii, const target_type & x) {
return ppdetail_atomic<target_type>::print_upto(pps, x); return ppdetail_atomic<target_type>::print_pretty(ppii, x);
}
static void print_pretty(ppstate * pps, const target_type & x) {
ppdetail_atomic<target_type>::print_pretty(pps, x);
} }
}; };
} }

View file

@ -23,11 +23,14 @@ namespace xo {
* Pretty-printer will introduce newlines if needed * Pretty-printer will introduce newlines if needed
* to stay to the left of this margin * to stay to the left of this margin
**/ **/
std::uint32_t right_margin_ = 135; std::uint32_t right_margin_ = 80;
/** amount of additional indent per nesting level **/ /** amount of additional indent per nesting level **/
std::uint32_t indent_width_ = 2; std::uint32_t indent_width_ = 2;
/** assert if attempting this much indent **/
std::uint32_t assert_indent_threshold = 10000;
///@} ///@}
}; };
} /*namespace print*/ } /*namespace print*/

View file

@ -11,13 +11,34 @@
namespace xo { namespace xo {
namespace print { namespace print {
struct ppstate; // see pretty.hpp struct ppstate; // see pretty.hpp
struct ppindentinfo;
// Defining this means ppdetail_atomic is not used. // Defining this means ppdetail_atomic is not used.
// In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type, // In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type,
// instead of giving compile-time error about missing template specialization of ppdetail // instead of giving compile-time error about missing template specialization of ppdetail.
#define ppdetail_atomic ppdetail #define ppdetail_atomic ppdetail
struct ppindentinfo {
ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto)
: pps_{pps}, ci0_{ci0}, ci1_{ci0 + indent_width}, upto_{upto} {}
ppstate * pps() const { return pps_; }
std::uint32_t ci1() const { return ci1_; }
bool upto() const { return upto_; }
private:
ppstate * pps_ = nullptr;
/** current indent **/
std::uint32_t ci0_ = 0;
/** ci0 +1 indent level **/
std::uint32_t ci1_ = 0;
/**
* true -> print on remainder of current line, unless past right margin
* false -> pretty across across multiple lines
**/
bool upto_;
};
/** @class ppdetail /** @class ppdetail
* @brief template for opt-in to pretty-printer * @brief template for opt-in to pretty-printer
* *
@ -32,88 +53,76 @@ namespace xo {
template <typename T> template <typename T>
struct ppdetail; struct ppdetail;
template <typename T>
struct ppdetail_atomic;
template <typename T> template <typename T>
struct ppdetail_atomic { struct ppdetail_atomic {
/** 1. print @p x to private stream @ref scratch_ss_ /** upto=true:
* 1. print @p x to private stream @ref scratch_ss_
* 2. return true iff N = number of characters printed satisfies N <= @p budget. * 2. return true iff N = number of characters printed satisfies N <= @p budget.
* content actually printed to *sbuf may be used as-is in this case * content actually printed to *sbuf may be used as-is in this case
* 3. return false otherwise. Will trigger non-degenerate pretty-printing. * 3. return false otherwise. Will trigger non-degenerate pretty-printing.
* 4. in any case consume some of @ref scratch_sbuf_ * 4. in any case consume some of @ref scratch_sbuf_
*
* upto=false:
* print @p x to private stream @ref scratch_ss_
**/ **/
static bool print_upto(ppstate * pps, const T & x); static bool print_pretty(const ppindentinfo & ppii, const T & x);
/** print @p x to private stream @ref scratch_ss_ **/
static void print_pretty(ppstate * pps, const T & x);
}; };
#ifndef ppdetail_atomic #ifndef ppdetail_atomic
template <int N> template <int N>
struct ppdetail<const char[N]> { struct ppdetail<const char[N]> {
using target_type = const char[N]; using target_type = const char[N];
static bool print_pretty(const ppindentinfo & ppii, const target_type & x) {
static bool print_upto(ppstate * pps, const target_type & x) { return ppdetail_atomic<target_type>::print_pretty(ppii, x);
return ppdetail_atomic<target_type>::print_upto(pps, x);
}
static void print_pretty(ppstate * pps, const target_type & x) {
ppdetail_atomic<target_type>::print_pretty(pps, x);
} }
}; };
template <> template <>
struct ppdetail<const char *> { struct ppdetail<const char *> {
static bool print_upto(ppstate * pps, const char * x) { static bool print_pretty(const ppindentinfo & ppii, const char * x) {
return ppdetail_atomic<const char *>::print_upto(pps, x); return ppdetail_atomic<const char *>::print_pretty(ppii, x);
}
static void print_pretty(ppstate * pps, const char * x) {
ppdetail_atomic<const char *>::print_pretty(pps, x);
} }
}; };
#define PPDETAIL_ATOMIC_BODY(target_type) \ #define PPDETAIL_ATOMIC_BODY(target_type) \
struct ppdetail<target_type> { \ struct ppdetail<target_type> { \
static bool print_upto(ppstate * pps, const target_type & x) { \ static bool print_pretty(const ppindentinfo & ppii, \
return ppdetail_atomic<target_type>::print_upto(pps, x); \ const target_type & x) { \
} \ return ppdetail_atomic<target_type>::print_pretty(ppii, x); \
\ } \
static void print_pretty(ppstate * pps, const target_type & x) { \
ppdetail_atomic<target_type>::print_pretty(pps, x); \
} \
} }
#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \ #define PPDETAIL_ATOMIC_BODY_CONST(target_type) \
struct ppdetail<const target_type> { \ struct ppdetail<const target_type> { \
static bool print_upto(ppstate * pps, const target_type & x) { \ static bool print_pretty(const ppindentinfo & ppii, \
return ppdetail_atomic<target_type>::print_upto(pps, x); \ const target_type & x) { \
} \ return ppdetail_atomic<target_type>::print_pretty(ppii, x); \
\ } \
static void print_pretty(ppstate * pps, const target_type & x) { \
ppdetail_atomic<target_type>::print_pretty(pps, x); \
} \
} }
#define PPDETAIL_ATOMIC(target_type) \ #define PPDETAIL_ATOMIC(target_type) \
template<> \ template<> \
PPDETAIL_ATOMIC_BODY(target_type) PPDETAIL_ATOMIC_BODY(target_type)
#define PPDETAIL_ATOMIC_CONST(target_type) \ #define PPDETAIL_ATOMIC_CONST(target_type) \
template<> \ template<> \
PPDETAIL_ATOMIC_BODY_CONST(target_type) PPDETAIL_ATOMIC_BODY_CONST(target_type)
PPDETAIL_ATOMIC(bool); PPDETAIL_ATOMIC(bool);
PPDETAIL_ATOMIC(char); PPDETAIL_ATOMIC(char);
// PPDETAIL_ATOMIC_CONST(char);
PPDETAIL_ATOMIC(std::int64_t); PPDETAIL_ATOMIC(std::int64_t);
PPDETAIL_ATOMIC(std::uint64_t); PPDETAIL_ATOMIC(std::uint64_t);
PPDETAIL_ATOMIC(std::int32_t); PPDETAIL_ATOMIC(std::int32_t);
PPDETAIL_ATOMIC(std::uint32_t); PPDETAIL_ATOMIC(std::uint32_t);
PPDETAIL_ATOMIC(float);
PPDETAIL_ATOMIC(double);
PPDETAIL_ATOMIC(std::string); PPDETAIL_ATOMIC(std::string);
PPDETAIL_ATOMIC(std::string_view);
using voidptr = void*; using voidptr = void*;
PPDETAIL_ATOMIC(voidptr); PPDETAIL_ATOMIC(voidptr);
#endif #endif
} } /*namespace print*/
} } /*namespace xo*/

View file

@ -94,44 +94,45 @@ namespace xo {
/** implement pretty-printing for template @c ppconcat r**/ /** implement pretty-printing for template @c ppconcat r**/
template <typename... Args> template <typename... Args>
struct ppdetail<ppconcat<Args...>> { struct ppdetail<ppconcat<Args...>> {
/** try to print @p target on one line. /** upto=true:
* try to print @p target on one line.
* return false if budget (space until right margin) exhausted * return false if budget (space until right margin) exhausted
* or if an embedded newline appears * or if an embedded newline appears
* *
* @return true on success, otherwise false. * @return true on success, otherwise false.
*
* upto=false:
* pretty-print @p target using multiple lines
**/ **/
static bool print_upto(ppstate * pps, ppconcat<Args...> target) { static bool print_pretty(const ppindentinfo & ppii, ppconcat<Args...> target) {
std::uint32_t saved = pps->pos(); if (ppii.upto()) {
std::uint32_t saved = ppii.pps()->pos();
if (!pps->has_margin()) if (!ppii.pps()->has_margin())
return false; return false;
if (std::apply( if (std::apply(
[pps](auto &&... args) { [ppii](auto &&... args) {
return detail::ppconcat_printupto_aux(pps, std::forward<decltype(args)>(args)...); return detail::ppconcat_printupto_aux(ppii.pps(), std::forward<decltype(args)>(args)...);
},
std::forward<std::tuple<Args...>>(target.contents_)
) == false)
{
return false;
}
return ppii.pps()->scan_no_newline(saved);
} else {
std::apply(
[ppii](auto &&... args) {
detail::ppconcat_print_pretty_aux(ppii.pps(), ppii.ci1(),
std::forward<decltype(args)>(args)...);
}, },
std::forward<std::tuple<Args...>>(target.contents_) std::forward<std::tuple<Args...>>(target.contents_)
) == false) );
{
return false; return false;
} }
return pps->scan_no_newline(saved);
}
/** pretty-print @p target using multiple lines
**/
static void print_pretty(ppstate * pps, ppconcat<Args...> target) {
std::uint32_t ci0 = pps->lpos();
std::uint32_t ci1 = ci0 + pps->indent_width();
std::apply(
[pps, ci1](auto &&... args) {
detail::ppconcat_print_pretty_aux(pps, ci1,
std::forward<decltype(args)>(args)...);
},
std::forward<std::tuple<Args...>>(target.contents_)
);
} }
}; };
} /*namespace print*/ } /*namespace print*/

View file

@ -8,6 +8,7 @@
#include "xo/indentlog/print/ppconfig.hpp" #include "xo/indentlog/print/ppconfig.hpp"
#include "xo/indentlog/log_streambuf.hpp" #include "xo/indentlog/log_streambuf.hpp"
#include "ppdetail_atomic.hpp" #include "ppdetail_atomic.hpp"
#include "tag.hpp"
#include "pad.hpp" #include "pad.hpp"
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@ -16,12 +17,10 @@
namespace xo { namespace xo {
namespace print { namespace print {
/** @class ppstate /** @class ppstate
* @brief hold pretty-printer state * @brief hold pretty-printer state
* *
* Use: * Use:
*
* ppconfig ppc; * ppconfig ppc;
* ppstate pps(&cout, 0, &ppc); * ppstate pps(&cout, 0, &ppc);
* *
@ -31,14 +30,19 @@ namespace xo {
struct ppstate { struct ppstate {
using streambuf_type = log_streambuf<char, std::char_traits<char>>; using streambuf_type = log_streambuf<char, std::char_traits<char>>;
explicit ppstate(std::uint32_t ci, const ppconfig * config, std::ostream * scratch_ss, streambuf_type * scratch_sbuf) explicit ppstate(std::uint32_t ci, const ppconfig * config,
std::ostream * scratch_ss, streambuf_type * scratch_sbuf)
: scratch_sbuf_{scratch_sbuf}, scratch_ss_{scratch_ss}, : scratch_sbuf_{scratch_sbuf}, scratch_ss_{scratch_ss},
current_indent_{ci}, config_{config} current_indent_{ci}, config_{config}
{} {}
uint32_t indent_width() const { return config_->indent_width_; } uint32_t indent_width() const { return config_->indent_width_; }
ppindentinfo indent_info(bool upto) { return ppindentinfo(this, lpos(), indent_width(), upto); }
std::uint32_t pos() const { return scratch_sbuf_->pos(); } std::uint32_t pos() const { return scratch_sbuf_->pos(); }
/** current position relative to start of line (last \n or \r),
* excluding color escape sequences
**/
std::uint32_t lpos() const { return scratch_sbuf_->lpos(); } std::uint32_t lpos() const { return scratch_sbuf_->lpos(); }
/** space available from current position until @c ppconfig.right_margin_ /** space available from current position until @c ppconfig.right_margin_
@ -60,8 +64,8 @@ namespace xo {
return avail_margin() >= budget; return avail_margin() >= budget;
} }
/** true if no newlines in range [@p lo, pos), /** true if no newlines in range [s, pos),
* where pos is current position * where s is @p start and pos is current position
**/ **/
bool scan_no_newline(std::uint32_t start) const { bool scan_no_newline(std::uint32_t start) const {
char const * p = scratch_sbuf_->lo() + start; char const * p = scratch_sbuf_->lo() + start;
@ -76,10 +80,13 @@ namespace xo {
} }
void indent(std::uint32_t tab) { void indent(std::uint32_t tab) {
assert(tab < config_->assert_indent_threshold);
(*scratch_ss_) << spaces(tab); (*scratch_ss_) << spaces(tab);
} }
/** write (to scratch stream) newline followed by @p tab spaces **/
void newline_indent(std::uint32_t tab) { void newline_indent(std::uint32_t tab) {
(*scratch_ss_) << "\n"; (*scratch_ss_) << "\n";
this->indent(tab); this->indent(tab);
} }
@ -94,12 +101,50 @@ namespace xo {
virtual void commit() {} virtual void commit() {}
// pretty-printing helpers
/** pretty-print empty struct **/
template <typename StructName, typename... Members>
std::uint32_t pretty_struct(const ppindentinfo & ppii, StructName && structname, Members&&...);
/** auxiliary function supporting @ref pretty_stuct .
* pretty-print a single member name on behalf of a struct/class.
* @return true iff printed content fits on remainder of line before right margin
**/
template <typename Member, typename... Rest>
std::uint32_t print_upto_struct_members(const ppindentinfo & ppii,
Member && member,
Rest&&... rest);
/** base-case overload **/
std::uint32_t print_upto_struct_members(const ppindentinfo &) { return true; }
/** pretty-print newline-and-indent separated member values.
* For struct members, use refrtag(name,value)
**/
template <typename Member, typename... Rest>
void pretty_struct_members(const ppindentinfo & ppii,
Member && member,
Rest&&... rest);
/** base-case overload **/
void pretty_struct_members(const ppindentinfo &) {}
template <typename T> template <typename T>
bool print_upto(T && x); bool print_upto(T && x);
template <typename Name, typename Value>
bool print_upto_tag(Name && name, Value && value);
template <typename T> template <typename T>
ppstate& pretty(T && x); ppstate& pretty(T && x);
/** write (to internal scratch stream), in order:
* newline, indent to @p ci, tag @p name, space, @p value
**/
template <typename Name, typename Value>
ppstate& newline_pretty_tag(std::uint32_t ci, Name && name, Value && value);
/** like pretty(x), but follow with trailing \n **/ /** like pretty(x), but follow with trailing \n **/
template <typename T> template <typename T>
ppstate& prettyn(T && x); ppstate& prettyn(T && x);
@ -165,6 +210,7 @@ namespace xo {
void check_commit() { void check_commit() {
if (pps_) { if (pps_) {
/* nuke state to prevent duplicate commit */
ppstate * pps = pps_; ppstate * pps = pps_;
pps_ = nullptr; pps_ = nullptr;
if (pps->decr_nesting_level() == 0) if (pps->decr_nesting_level() == 0)
@ -175,42 +221,59 @@ namespace xo {
ppstate * pps_ = nullptr; ppstate * pps_ = nullptr;
}; };
template <typename T> template <typename StructName, typename... Members>
bool std::uint32_t
ppdetail_atomic<T>::print_upto(ppstate * pps, const T & x) ppstate::pretty_struct(const ppindentinfo & ppii, StructName && structname, Members&&... members)
{ {
/* position before we write */ if (ppii.upto()) {
if (!pps->has_margin()) return (this->print_upto("<")
&& this->print_upto(structname)
&& this->print_upto_struct_members(ppii, members...)
&& this->print_upto(">"));
} else {
this->write("<");
this->write(structname);
this->pretty_struct_members(ppii, members...);
this->write(">");
return false; return false;
}
std::uint32_t start = pps->pos();
/* if x is non-atomic may optimize by checking budget more often */
pps->write(x);
if (!pps->has_margin())
return false;
/* scan characters [lo, hi) in line buffer.
* Newline -> caller should use print_pretty()
*/
return (pps->scan_no_newline(start));
} }
template <typename T> template <typename Member, typename... Rest>
void std::uint32_t
ppdetail_atomic<T>::print_pretty(ppstate * pps, const T & x) ppstate::print_upto_struct_members(const ppindentinfo & ppii,
Member && member,
Rest&&... rest)
{ {
/* In default implementation we don't know where to introduce newlines. if (this->print_upto(" ") && this->print_upto(member))
* Still need to calculate lpos though return this->print_upto_struct_members(ppii, rest...);
*/
pps->write(x); return false;
}
template <typename Member, typename... Rest>
void
ppstate::pretty_struct_members(const ppindentinfo & ppii,
Member && member,
Rest&&... rest)
{
newline_indent(ppii.ci1());
this->pretty(member);
this->pretty_struct_members(ppii, rest...);
} }
template <typename T> template <typename T>
bool bool
ppstate::print_upto(T && value) { ppstate::print_upto(T && value) {
return ppdetail<std::decay_t<T>>::print_upto(this, value); std::uint32_t saved = pos();
if (!has_margin())
return false;
if (!ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(true /*upto*/), value))
return false;
return scan_no_newline(saved);
} }
template <typename T> template <typename T>
@ -220,12 +283,13 @@ namespace xo {
* Decision depends on ancestor context * Decision depends on ancestor context
*/ */
std::uint32_t saved = pos(); auto saved = scratch_sbuf_->checkpoint();
ppcommitter ppc(this); ppcommitter ppc(this);
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ /* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, static_cast<const T &>(value)); bool fits = ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(true /*upto*/),
static_cast<const T &>(value));
if (fits) { if (fits) {
/* fits on 1 line -> pretty-printing maybe not required */ /* fits on 1 line -> pretty-printing maybe not required */
@ -235,7 +299,7 @@ namespace xo {
/* here: didn't fit -> split over multiple lines */ /* here: didn't fit -> split over multiple lines */
this->scratch_sbuf_->rewind_to(saved); this->scratch_sbuf_->rewind_to(saved);
ppdetail<std::decay_t<T>>::print_pretty(this, value); ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(false), value);
ppc.check_commit(); ppc.check_commit();
return *this; return *this;
@ -248,12 +312,12 @@ namespace xo {
* Decision depends on ancestor context * Decision depends on ancestor context
*/ */
std::uint32_t saved = pos(); auto saved = scratch_sbuf_->checkpoint();
ppcommitter ppc(this); ppcommitter ppc(this);
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ /* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, value); bool fits = ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(true), value);
if (fits) { if (fits) {
this->newline_indent(0); this->newline_indent(0);
@ -264,12 +328,121 @@ namespace xo {
/* here: didn't fit -> split over multiple lines */ /* here: didn't fit -> split over multiple lines */
this->scratch_sbuf_->rewind_to(saved); this->scratch_sbuf_->rewind_to(saved);
ppdetail<std::remove_reference_t<T>>::print_pretty(this, value); ppdetail<std::remove_reference_t<T>>::print_pretty(this->indent_info(false), value);
this->newline_indent(0); this->newline_indent(0);
ppc.check_commit(); ppc.check_commit();
return *this; return *this;
} }
template <typename T>
bool
ppdetail_atomic<T>::print_pretty(const ppindentinfo & ppii, const T & x)
{
if (ppii.upto()) {
std::uint32_t start = ppii.pps()->pos();
/* if x is non-atomic may optimize by checking budget more often */
ppii.pps()->write(x);
if (!ppii.pps()->has_margin())
return false;
/* scan characters [lo, hi) in line buffer.
* Newline -> caller should use print_pretty()
*/
return (ppii.pps()->scan_no_newline(start));
} else {
/* In default implementation we don't know where to introduce newlines.
* Still need to calculate lpos though
*/
ppii.pps()->write(x);
return false;
}
assert(false);
return true;
}
namespace detail {
template <typename Tag>
struct ppdetail_tag {
static bool print_pretty(const ppindentinfo & ppii,
const Tag & tag)
{
if (ppii.upto()) {
if (tag.prefix_space())
ppii.pps()->write(" ");
/* must color here, because we may keep the output if it fits! */
if (!ppii.pps()->print_upto(with_color(color_spec_type::yellow(), // tag_config::tag_color,
concat((char const *)":", tag.name()))))
return false;
ppii.pps()->write(" ");
if (!ppii.pps()->print_upto(tag.value()))
return false;
return true;
} else {
/* pretty-print like
* :somename
* pretty(value)
*/
if (tag.prefix_space())
ppii.pps()->write(" ");
ppii.pps()->write(with_color(color_spec_type::yellow(), //tag_config::tag_color,
concat((char const *)":", tag.name())));
ppii.pps()->newline_indent(ppii.ci1());
ppii.pps()->pretty(tag.value());
return false;
}
}
};
}
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
struct ppdetail<tag_impl<PrefixSpace, TagStyle, Name, Value>> {
static bool print_pretty(const ppindentinfo & ppii,
const tag_impl<PrefixSpace, TagStyle, Name, Value> & tag)
{
using tag_type = tag_impl<PrefixSpace, TagStyle, Name, Value>;
return detail::ppdetail_tag<tag_type>::print_pretty(ppii, tag);
}
};
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
struct ppdetail<ref_tag_impl<PrefixSpace, TagStyle, Name, Value>> {
static bool print_pretty(const ppindentinfo & ppii,
const ref_tag_impl<PrefixSpace, TagStyle, Name, Value> & tag)
{
using tag_type = ref_tag_impl<PrefixSpace, TagStyle, Name, Value>;
return detail::ppdetail_tag<tag_type>::print_pretty(ppii, tag);
}
};
template <typename Name, typename Value>
bool
ppstate::print_upto_tag(Name && name, Value && value)
{
return this->print_upto(xrtag(name, value));
}
template <typename Name, typename Value>
ppstate &
ppstate::newline_pretty_tag(std::uint32_t ci, Name && name, Value && value)
{
newline_indent(ci);
this->pretty(rtag(name, value));
return *this;
}
} /*namespace print*/ } /*namespace print*/
} /*namespace xo*/ } /*namespace xo*/

View file

@ -1,14 +0,0 @@
/* @file pretty_concat.hpp
*
* author: Roland Conybeare, Jul 2025
*/
#pragma once
#include "concat.hpp"
#include "pretty.hpp"
namespace xo {
}
/* end pretty_concat.hpp */

View file

@ -1,56 +0,0 @@
/* file pretty_tag.hpp
*
* author: Roland Conybeare, Jul 2025
*/
#pragma once
#include "pretty.hpp"
#include "tag.hpp"
namespace xo {
namespace print {
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
struct ppdetail<tag_impl<PrefixSpace, TagStyle, Name, Value>> {
static bool print_upto(ppstate * pps, const tag_impl<PrefixSpace, TagStyle, Name, Value> & tag) {
std::uint32_t saved = pps->pos();
if (!pps->has_margin())
return false;
if (PrefixSpace)
pps->write(" ");
/* skil colorizing here, since doesn't consume visible space */
if (!pps->print_upto(concat((char const *)":", concat(tag.name(), (char const *)" "))))
return false;
if (!pps->print_upto(quot_impl(TagStyle == tagstyle::autoescape, tag.value())))
return false;
return pps->scan_no_newline(saved);
}
static void print_pretty(ppstate * pps, const tag_impl<PrefixSpace, TagStyle, Name, Value> & tag) {
/* pretty-print like
* :somename
* pretty(value)
*/
std::uint32_t ci0 = pps->lpos();
std::uint32_t ci1 = ci0 + pps->indent_width();
if (PrefixSpace)
pps->write(" ");
pps->write(with_color(tag_config::tag_color,
concat((char const *)":", tag.name())));
pps->newline_indent(ci1);
pps->pretty(tag.value());
pps->newline_indent(ci0);
}
};
} /*namespace print*/
} /*namespace xo*/
/* end pretty_tag.hpp */

View file

@ -11,56 +11,47 @@
namespace xo { namespace xo {
namespace print { namespace print {
template <typename T> template <typename Vector>
struct ppdetail<std::vector<T>> { struct ppdetail_vector {
static bool print_upto(ppstate * pps, const std::vector<T> & x) { static bool print_pretty(const ppindentinfo & ppii, const Vector & x) {
std::uint32_t saved = pps->pos(); if (ppii.upto()) {
if (!pps->has_margin()) ppii.pps()->write("[");
for (size_t i = 0, z = x.size(); i < z; ++i) {
if (i > 0)
ppii.pps()->write(", ");
if (!ppii.pps()->print_upto(x[i]))
return false;
if (!ppii.pps()->has_margin())
return false;
}
ppii.pps()->write("]");
return true;
} else {
ppii.pps()->write('[');
for (size_t i = 0, z = x.size(); i < z; ++i) {
if (i == 0)
ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1);
else
ppii.pps()->newline_indent(ppii.ci1());
ppii.pps()->pretty(x[i]);
if (i+1 < z)
ppii.pps()->write(',');
}
ppii.pps()->write(" ]");
return false; return false;
pps->write("[");
for (size_t i = 0, z = x.size(); i < z; ++i) {
if (i > 0)
pps->write(", ");
if (!pps->print_upto(x[i]))
return false;
if (!pps->has_margin())
return false;
} }
pps->write("]");
if (!pps->has_margin())
return false;
return pps->scan_no_newline(saved);
}
static void print_pretty(ppstate * pps, const std::vector<T> & x) {
std::uint32_t ci0 = pps->lpos();
pps->write('[');
std::uint32_t ci1 = ci0 + pps->indent_width();
for (size_t i = 0, z = x.size(); i < z; ++i) {
pps->newline_indent(ci1);
pps->pretty(x[i]);
if (i+1 < z)
pps->write(',');
}
pps->newline_indent(ci0);
pps->write(']');
} }
}; };
template <typename T> template <typename T>
struct ppdetail<const std::vector<T>> { struct ppdetail<std::vector<T>> {
static bool print_upto(ppstate * pps, const std::vector<T> & x) { static bool print_pretty(const ppindentinfo & ppii, const std::vector<T> & x) {
return ppdetail<std::vector<T>>::print_upto(pps, x); return ppdetail_vector<std::vector<T>>::print_pretty(ppii, x);
}
static void print_pretty(ppstate * pps, const std::vector<T> & x) {
ppdetail<std::vector<T>>::print_pretty(pps, x);
} }
}; };
} }

View file

@ -103,13 +103,13 @@ namespace xo {
} /*print*/ } /*print*/
private: private:
/* .unq_flag: if true, omit surrounding " chars /** .unq_flag: if true, omit surrounding " chars
* whenever printed value satisfies both: * whenever printed value satisfies both:
* - no escaped chars * - no escaped chars
* - no spaces * - no spaces
*/ **/
bool unq_flag_ = false; bool unq_flag_ = false;
/* .value: value to be printed */ /** .value: value to be printed **/
T value_; T value_;
}; /*quot_impl*/ }; /*quot_impl*/
@ -120,7 +120,7 @@ namespace xo {
return os; return os;
} /*operator*/ } /*operator*/
/* writing out std::forward<T> behavior for completeness' sake: /** writing out std::forward<T> behavior for completeness' sake:
* *
* 1. call quoted(x) with rvalue std::string x, then: * 1. call quoted(x) with rvalue std::string x, then:
* - T will be deduced to [std::string] * - T will be deduced to [std::string]
@ -134,7 +134,7 @@ namespace xo {
* 2b. call quoted(x) with std::string const & x, then: * 2b. call quoted(x) with std::string const & x, then:
* - T deduced to [std::string const &] * - T deduced to [std::string const &]
* - std::string const & passed to quot_impl ctor * - std::string const & passed to quot_impl ctor
*/ **/
template<typename T> template<typename T>
auto quot(T && x) { auto quot(T && x) {
return quot_impl(false /*unq_flag*/, std::forward<T>(x)); return quot_impl(false /*unq_flag*/, std::forward<T>(x));

View file

@ -30,7 +30,7 @@ namespace xo {
raw, raw,
}; };
/** K,V pair for printing. /** key-value pair for printing.
* *
* @tparam PrefixSpace if true print one space before :K * @tparam PrefixSpace if true print one space before :K
* @tparam TagStyle controls printing format * @tparam TagStyle controls printing format
@ -63,6 +63,8 @@ namespace xo {
tag_impl(Name && n, Value && v) tag_impl(Name && n, Value && v)
: name_{std::forward<Name>(n)}, value_{std::forward<Value>(v)} {} : name_{std::forward<Name>(n)}, value_{std::forward<Value>(v)} {}
constexpr bool prefix_space() const { return PrefixSpace; }
Name const & name() const { return name_; } Name const & name() const { return name_; }
Value const & value() const { return value_; } Value const & value() const { return value_; }
@ -71,20 +73,52 @@ namespace xo {
Value value_; Value value_;
}; };
/** key-value pair for printing.
* Like tag_impl<..> but uses reference for value.
* Use in context where value can't be a temporary rvalue.
**/
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
struct ref_tag_impl {
ref_tag_impl(Name const & n, Value const & v) : name_{n}, value_{v} {}
ref_tag_impl(Name && n, Value && v) : name_{std::forward<Name>(n)}, value_{std::forward<Value>(v)} {}
constexpr bool prefix_space() const { return PrefixSpace; }
Name const & name() const { return name_; }
Value const & value() const { return value_; }
private:
Name name_;
const Value& value_;
};
// ----- xtag ----- // ----- xtag -----
/** Write name,value pair @p n, @p v with format like
* :name value
* Precede with initial space
* Escape whitespace/special characters in @p v
**/
template<typename Name, typename Value> template<typename Name, typename Value>
auto //tag_impl<true, tagstyle::autoescape, Name, std::decay_t<Value>> auto
xtag(Name && n, Value && v) xtag(Name && n, Value && v)
{ {
return tag_impl<true, tagstyle::autoescape, Name, std::decay_t<Value>>(n, v); return tag_impl<true, tagstyle::autoescape, std::decay_t<Name>, std::decay_t<Value>>(n, v);
} /*xtag*/ } /*xtag*/
template<typename Value> // ----- xrtag ----
auto //tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>
xtag(char const * n, Value && v) { /** Write name,value pair @p n, @p v with format like
return tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>(n, v); * :name value
} /*xtag*/ * Precede with initial space.
* Do not escape whitespace/special characters in @p v.
**/
template<typename Name, typename Value>
auto
xrtag(Name && n, Value && v)
{
return tag_impl<true, tagstyle::raw, std::decay_t<Name>, std::decay_t<Value>>(n, v);
}
// ----- tag ----- // ----- tag -----
@ -102,6 +136,28 @@ namespace xo {
return tag_impl<false, tagstyle::autoescape, char const *, Value>(n, v); return tag_impl<false, tagstyle::autoescape, char const *, Value>(n, v);
} }
// ----- rtag -----
template<typename Name, typename Value>
auto
rtag(Name && n, Value && v)
{
return tag_impl<false, tagstyle::raw, std::decay_t<Name>, std::decay_t<Value>>(n, v);
}
// ----- refrtag -----
/** 'reference raw tag'.
* 1. @p v must survive until refrtag is used (i.e. until tag inserted into some stream)
* 2. don't escape whitespace when printing value (pretty-printing with parseable structure)
**/
template <typename Name, typename Value>
auto
refrtag(Name && n, Value && v)
{
return ref_tag_impl<false, tagstyle::raw, std::decay_t<Name>, std::decay_t<Value>>(n, v);
}
// ----- operator<< on tag_impl ----- // ----- operator<< on tag_impl -----
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value> template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
@ -124,6 +180,29 @@ namespace xo {
return s; return s;
} /*operator<<*/ } /*operator<<*/
// ----- operator<< on ref_tag_impl -----
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
inline std::ostream &
operator<<(std::ostream &s,
ref_tag_impl<PrefixSpace, TagStyle, Name, Value> const & tag)
{
using xo::print::unq;
if (PrefixSpace)
s << " ";
s << with_color(tag_config::tag_color, concat((char const *)":", tag.name()))
<< " ";
if (TagStyle == tagstyle::autoescape)
s << unq(tag.value());
else
s << tag.value();
return s;
} /*operator<<*/
} /*namespace xo*/ } /*namespace xo*/
/* end tag.hpp */ /* end tag.hpp */

View file

@ -7,8 +7,6 @@
#include "print/filename.hpp" #include "print/filename.hpp"
#include "print/ppstr.hpp" #include "print/ppstr.hpp"
#include "print/tostr.hpp" #include "print/tostr.hpp"
#include "print/pretty_concat.hpp"
#include "print/pretty_tag.hpp"
#include <stdexcept> #include <stdexcept>
#include <cstdint> #include <cstdint>