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::
iff
| Short for "if and only if"
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
| 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>>
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:
log_streambuf(std::uint32_t buf_z, bool debug_flag = false) : debug_flag_{debug_flag} {
this->buf_v_.resize(buf_z);
@ -27,8 +37,15 @@ namespace xo {
char const * hi() const { return this->lo() + this->capacity(); }
std::uint32_t pos() const { return this->pptr() - this->pbase(); }
/** number of characters since start of line (last \n or \r) **/
std::uint32_t lpos() const { return pos() - solpos_; }
/** number of visible characters since start of line (last \n or \r) **/
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_; }
@ -40,12 +57,24 @@ namespace xo {
this->setp(p_lo, p_hi);
this->solpos_ = 0;
this->color_escape_chars_ = 0;
this->color_escape_start_ = nullptr;
} /*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 */
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:
@ -65,7 +94,7 @@ namespace xo {
this->setp(p_base, p_hi);
this->pbump(old_n);
} /*expand*/
}
virtual std::streamsize
xsputn(char const * s, std::streamsize n) override {
@ -94,7 +123,7 @@ namespace xo {
ncopied = n;
}
if (debug_flag_) {
if (false /*debug_flag_*/) {
std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)"
<< ", lo=" << (void*)this->pptr()
<< ", hi=" << (void*)(this->pptr() + n)
@ -103,11 +132,39 @@ namespace xo {
std::memcpy(this->pptr(), s, ncopied);
/* scan range [pptr, pptr+n] backwards, to account for newline (if any) */
for (char const * p_lo = this->pptr(), * p_hi = p_lo + n - 1, * p = p_hi; p >= p_lo; --p) {
/* scan range [pptr, pptr+n] for:
* 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') {
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*/
virtual int_type
overflow(int_type new_ch) override
{
char * old_pptr = this->pptr();
std::streamsize old_n = old_pptr - this->pbase();
overflow(int_type new_ch) override {
char * old_pptr = this->pptr();
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_) {
std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl;
}
if (debug_flag_) {
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->pbump(1);
this->buf_v_[old_n] = new_ch;
this->pbump(1);
if ((new_ch == static_cast<int_type>('\n')) || (new_ch == static_cast<int_type>('\r')))
this->solpos_ = this->pos();
if ((new_ch == static_cast<int_type>('\n')) || (new_ch == static_cast<int_type>('\r'))) {
this->solpos_ = this->pos();
}
if (new_ch == Traits::eof()) {
/* reminder: returning eof sets badbit on ostream */
return Traits::not_eof(new_ch);
} else {
return new_ch;
}
} /*overflow*/
if (new_ch == Traits::eof()) {
/* reminder: returning eof sets badbit on ostream */
return Traits::not_eof(new_ch);
} else {
return new_ch;
}
} /*overflow*/
/* off. offset, relative to starting point dir.
* dir.
@ -154,6 +211,9 @@ namespace xo {
std::ios_base::seekdir dir,
std::ios_base::openmode which) override {
//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
if (which != std::ios_base::out)
@ -179,6 +239,13 @@ namespace xo {
* Use to drive @ref lpos
**/
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 **/
std::vector<char> buf_v_;
/** true to debug log_streambuf itself **/

View file

@ -2,8 +2,8 @@
#pragma once
#include "ppdetail_atomic.hpp"
#include <ostream>
//#include <utility> // for std::move
#include <cstdint>
namespace xo {
@ -17,11 +17,11 @@ namespace xo {
inline std::ostream &
operator<< (std::ostream & os, color_encoding x) {
switch(x) {
case color_encoding::none: os << "none"; break;
case color_encoding::ansi: os << "ansi"; break;
case color_encoding::none: os << "none"; break;
case color_encoding::ansi: os << "ansi"; break;
case color_encoding::xterm: os << "xterm"; break;
case color_encoding::rgb: os << "rgb"; break;
default: os << "???"; break;
case color_encoding::rgb: os << "rgb"; break;
default: os << "???"; break;
}
return os;
} /*operator<<*/
@ -125,11 +125,11 @@ namespace xo {
} /*operator<<*/
enum class coloring_control_flags : std::uint8_t {
none = 0x0,
color_on = 0x01,
contents = 0x02,
none = 0x0,
color_on = 0x01,
contents = 0x02,
color_off = 0x04,
all = 0x07
all = 0x07
};
inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) {
@ -177,25 +177,42 @@ namespace xo {
template <typename 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));
} /*with_color*/
}
inline color_impl<int>
color_on(color_spec_type const & spec) {
return color_impl<int>(coloring_control_flags::color_on, spec, 0);
} /*color_on*/
}
inline color_impl<int>
color_off(color_spec_type const & spec) {
/* any spec other than color_spec_type::none() works here */
return color_impl<int>(coloring_control_flags::color_off, spec, 0);
} /*color_off*/
}
template <typename Contents>
inline std::ostream &
operator<<(std::ostream & os, color_impl<Contents> const & x) {
x.print(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*/

View file

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

View file

@ -23,11 +23,14 @@ namespace xo {
* Pretty-printer will introduce newlines if needed
* 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 **/
std::uint32_t indent_width_ = 2;
/** assert if attempting this much indent **/
std::uint32_t assert_indent_threshold = 10000;
///@}
};
} /*namespace print*/

View file

@ -11,13 +11,34 @@
namespace xo {
namespace print {
struct ppstate; // see pretty.hpp
struct ppindentinfo;
// Defining this means ppdetail_atomic is not used.
// 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
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
* @brief template for opt-in to pretty-printer
*
@ -32,88 +53,76 @@ namespace xo {
template <typename T>
struct ppdetail;
template <typename T>
struct ppdetail_atomic;
template <typename T>
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.
* content actually printed to *sbuf may be used as-is in this case
* 3. return false otherwise. Will trigger non-degenerate pretty-printing.
* 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);
/** print @p x to private stream @ref scratch_ss_ **/
static void print_pretty(ppstate * pps, const T & x);
static bool print_pretty(const ppindentinfo & ppii, const T & x);
};
#ifndef ppdetail_atomic
template <int N>
struct ppdetail<const char[N]> {
using target_type = const char[N];
static bool print_upto(ppstate * pps, const target_type & 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);
static bool print_pretty(const ppindentinfo & ppii, const target_type & x) {
return ppdetail_atomic<target_type>::print_pretty(ppii, x);
}
};
template <>
struct ppdetail<const char *> {
static bool print_upto(ppstate * pps, const char * x) {
return ppdetail_atomic<const char *>::print_upto(pps, x);
}
static void print_pretty(ppstate * pps, const char * x) {
ppdetail_atomic<const char *>::print_pretty(pps, x);
static bool print_pretty(const ppindentinfo & ppii, const char * x) {
return ppdetail_atomic<const char *>::print_pretty(ppii, x);
}
};
#define PPDETAIL_ATOMIC_BODY(target_type) \
struct ppdetail<target_type> { \
static bool print_upto(ppstate * pps, const target_type & 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); \
} \
#define PPDETAIL_ATOMIC_BODY(target_type) \
struct ppdetail<target_type> { \
static bool print_pretty(const ppindentinfo & ppii, \
const target_type & x) { \
return ppdetail_atomic<target_type>::print_pretty(ppii, x); \
} \
}
#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \
struct ppdetail<const target_type> { \
static bool print_upto(ppstate * pps, const target_type & 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); \
} \
#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \
struct ppdetail<const target_type> { \
static bool print_pretty(const ppindentinfo & ppii, \
const target_type & x) { \
return ppdetail_atomic<target_type>::print_pretty(ppii, x); \
} \
}
#define PPDETAIL_ATOMIC(target_type) \
template<> \
#define PPDETAIL_ATOMIC(target_type) \
template<> \
PPDETAIL_ATOMIC_BODY(target_type)
#define PPDETAIL_ATOMIC_CONST(target_type) \
template<> \
#define PPDETAIL_ATOMIC_CONST(target_type) \
template<> \
PPDETAIL_ATOMIC_BODY_CONST(target_type)
PPDETAIL_ATOMIC(bool);
PPDETAIL_ATOMIC(char);
// PPDETAIL_ATOMIC_CONST(char);
PPDETAIL_ATOMIC(std::int64_t);
PPDETAIL_ATOMIC(std::uint64_t);
PPDETAIL_ATOMIC(std::int32_t);
PPDETAIL_ATOMIC(std::uint32_t);
PPDETAIL_ATOMIC(float);
PPDETAIL_ATOMIC(double);
PPDETAIL_ATOMIC(std::string);
PPDETAIL_ATOMIC(std::string_view);
using voidptr = void*;
PPDETAIL_ATOMIC(voidptr);
#endif
}
}
} /*namespace print*/
} /*namespace xo*/

View file

@ -94,44 +94,45 @@ namespace xo {
/** implement pretty-printing for template @c ppconcat r**/
template <typename... 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
* or if an embedded newline appears
*
* @return true on success, otherwise false.
*
* upto=false:
* pretty-print @p target using multiple lines
**/
static bool print_upto(ppstate * pps, ppconcat<Args...> target) {
std::uint32_t saved = pps->pos();
static bool print_pretty(const ppindentinfo & ppii, ppconcat<Args...> target) {
if (ppii.upto()) {
std::uint32_t saved = ppii.pps()->pos();
if (!pps->has_margin())
return false;
if (!ppii.pps()->has_margin())
return false;
if (std::apply(
[pps](auto &&... args) {
return detail::ppconcat_printupto_aux(pps, std::forward<decltype(args)>(args)...);
if (std::apply(
[ppii](auto &&... 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_)
) == 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*/

View file

@ -8,6 +8,7 @@
#include "xo/indentlog/print/ppconfig.hpp"
#include "xo/indentlog/log_streambuf.hpp"
#include "ppdetail_atomic.hpp"
#include "tag.hpp"
#include "pad.hpp"
#include <sstream>
#include <vector>
@ -16,12 +17,10 @@
namespace xo {
namespace print {
/** @class ppstate
* @brief hold pretty-printer state
*
* Use:
*
* ppconfig ppc;
* ppstate pps(&cout, 0, &ppc);
*
@ -31,14 +30,19 @@ namespace xo {
struct ppstate {
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},
current_indent_{ci}, config_{config}
{}
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(); }
/** current position relative to start of line (last \n or \r),
* excluding color escape sequences
**/
std::uint32_t lpos() const { return scratch_sbuf_->lpos(); }
/** space available from current position until @c ppconfig.right_margin_
@ -60,8 +64,8 @@ namespace xo {
return avail_margin() >= budget;
}
/** true if no newlines in range [@p lo, pos),
* where pos is current position
/** true if no newlines in range [s, pos),
* where s is @p start and pos is current position
**/
bool scan_no_newline(std::uint32_t start) const {
char const * p = scratch_sbuf_->lo() + start;
@ -76,10 +80,13 @@ namespace xo {
}
void indent(std::uint32_t tab) {
assert(tab < config_->assert_indent_threshold);
(*scratch_ss_) << spaces(tab);
}
/** write (to scratch stream) newline followed by @p tab spaces **/
void newline_indent(std::uint32_t tab) {
(*scratch_ss_) << "\n";
this->indent(tab);
}
@ -94,12 +101,50 @@ namespace xo {
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>
bool print_upto(T && x);
template <typename Name, typename Value>
bool print_upto_tag(Name && name, Value && value);
template <typename T>
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 **/
template <typename T>
ppstate& prettyn(T && x);
@ -165,6 +210,7 @@ namespace xo {
void check_commit() {
if (pps_) {
/* nuke state to prevent duplicate commit */
ppstate * pps = pps_;
pps_ = nullptr;
if (pps->decr_nesting_level() == 0)
@ -175,42 +221,59 @@ namespace xo {
ppstate * pps_ = nullptr;
};
template <typename T>
bool
ppdetail_atomic<T>::print_upto(ppstate * pps, const T & x)
template <typename StructName, typename... Members>
std::uint32_t
ppstate::pretty_struct(const ppindentinfo & ppii, StructName && structname, Members&&... members)
{
/* position before we write */
if (!pps->has_margin())
if (ppii.upto()) {
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;
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>
void
ppdetail_atomic<T>::print_pretty(ppstate * pps, const T & x)
template <typename Member, typename... Rest>
std::uint32_t
ppstate::print_upto_struct_members(const ppindentinfo & ppii,
Member && member,
Rest&&... rest)
{
/* In default implementation we don't know where to introduce newlines.
* Still need to calculate lpos though
*/
pps->write(x);
if (this->print_upto(" ") && this->print_upto(member))
return this->print_upto_struct_members(ppii, rest...);
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>
bool
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>
@ -220,12 +283,13 @@ namespace xo {
* Decision depends on ancestor context
*/
std::uint32_t saved = pos();
auto saved = scratch_sbuf_->checkpoint();
ppcommitter ppc(this);
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, static_cast<const T &>(value));
/* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(true /*upto*/),
static_cast<const T &>(value));
if (fits) {
/* fits on 1 line -> pretty-printing maybe not required */
@ -235,7 +299,7 @@ namespace xo {
/* here: didn't fit -> split over multiple lines */
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();
return *this;
@ -248,12 +312,12 @@ namespace xo {
* Decision depends on ancestor context
*/
std::uint32_t saved = pos();
auto saved = scratch_sbuf_->checkpoint();
ppcommitter ppc(this);
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, value);
/* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
bool fits = ppdetail<std::decay_t<T>>::print_pretty(this->indent_info(true), value);
if (fits) {
this->newline_indent(0);
@ -264,12 +328,121 @@ namespace xo {
/* here: didn't fit -> split over multiple lines */
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);
ppc.check_commit();
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 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 print {
template <typename T>
struct ppdetail<std::vector<T>> {
static bool print_upto(ppstate * pps, const std::vector<T> & x) {
std::uint32_t saved = pps->pos();
if (!pps->has_margin())
template <typename Vector>
struct ppdetail_vector {
static bool print_pretty(const ppindentinfo & ppii, const Vector & x) {
if (ppii.upto()) {
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;
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>
struct ppdetail<const std::vector<T>> {
static bool print_upto(ppstate * pps, const std::vector<T> & x) {
return ppdetail<std::vector<T>>::print_upto(pps, x);
}
static void print_pretty(ppstate * pps, const std::vector<T> & x) {
ppdetail<std::vector<T>>::print_pretty(pps, x);
struct ppdetail<std::vector<T>> {
static bool print_pretty(const ppindentinfo & ppii, const std::vector<T> & x) {
return ppdetail_vector<std::vector<T>>::print_pretty(ppii, x);
}
};
}

View file

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

View file

@ -30,7 +30,7 @@ namespace xo {
raw,
};
/** K,V pair for printing.
/** key-value pair for printing.
*
* @tparam PrefixSpace if true print one space before :K
* @tparam TagStyle controls printing format
@ -63,6 +63,8 @@ namespace xo {
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_; }
@ -71,20 +73,52 @@ namespace xo {
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 -----
/** 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>
auto //tag_impl<true, tagstyle::autoescape, Name, std::decay_t<Value>>
auto
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*/
template<typename Value>
auto //tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>
xtag(char const * n, Value && v) {
return tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>(n, v);
} /*xtag*/
// ----- xrtag ----
/** Write name,value pair @p n, @p v with format like
* :name value
* 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 -----
@ -102,6 +136,28 @@ namespace xo {
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 -----
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
@ -124,6 +180,29 @@ namespace xo {
return s;
} /*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*/
/* end tag.hpp */

View file

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