diff --git a/docs/glossary.rst b/docs/glossary.rst index 82bd0d2..0631a3f 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -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" diff --git a/include/xo/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp index 855fc05..b6e9305 100644 --- a/include/xo/indentlog/log_streambuf.hpp +++ b/include/xo/indentlog/log_streambuf.hpp @@ -16,6 +16,16 @@ namespace xo { **/ template > 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(this->buf_v_.size())); + assert(old_n <= static_cast(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('\n')) || (new_ch == static_cast('\r'))) - this->solpos_ = this->pos(); + if ((new_ch == static_cast('\n')) || (new_ch == static_cast('\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 buf_v_; /** true to debug log_streambuf itself **/ diff --git a/include/xo/indentlog/print/color.hpp b/include/xo/indentlog/print/color.hpp index bcd3e63..ef91f49 100644 --- a/include/xo/indentlog/print/color.hpp +++ b/include/xo/indentlog/print/color.hpp @@ -2,8 +2,8 @@ #pragma once +#include "ppdetail_atomic.hpp" #include -//#include // for std::move #include 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 color_impl with_color(color_spec_type const & spec, Contents && contents) { return color_impl(coloring_control_flags::all, spec, std::forward(contents)); - } /*with_color*/ + } inline color_impl color_on(color_spec_type const & spec) { return color_impl(coloring_control_flags::color_on, spec, 0); - } /*color_on*/ + } inline color_impl color_off(color_spec_type const & spec) { /* any spec other than color_spec_type::none() works here */ return color_impl(coloring_control_flags::color_off, spec, 0); - } /*color_off*/ + } template inline std::ostream & operator<<(std::ostream & os, color_impl 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 + struct ppdetail> { + using target_type = color_impl; + + static bool print_pretty(const ppindentinfo & ppii, const target_type & x) { + return ppdetail_atomic::print_pretty(ppii, x); + } + }; + } +#endif } /*namespace xo*/ diff --git a/include/xo/indentlog/print/concat.hpp b/include/xo/indentlog/print/concat.hpp index 3c166c4..e9d4905 100644 --- a/include/xo/indentlog/print/concat.hpp +++ b/include/xo/indentlog/print/concat.hpp @@ -48,11 +48,8 @@ namespace xo { struct ppdetail> { using target_type = concat_impl; - static bool print_upto(ppstate * pps, const target_type & x) { - return ppdetail_atomic::print_upto(pps, x); - } - static void print_pretty(ppstate * pps, const target_type & x) { - ppdetail_atomic::print_pretty(pps, x); + static bool print_pretty(const ppindentinfo & ppii, const target_type & x) { + return ppdetail_atomic::print_pretty(ppii, x); } }; } diff --git a/include/xo/indentlog/print/ppconfig.hpp b/include/xo/indentlog/print/ppconfig.hpp index 1cc6059..67d1831 100644 --- a/include/xo/indentlog/print/ppconfig.hpp +++ b/include/xo/indentlog/print/ppconfig.hpp @@ -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*/ diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 05614ee..8982402 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -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 struct ppdetail; - template - struct ppdetail_atomic; - template 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 struct ppdetail { using target_type = const char[N]; - - static bool print_upto(ppstate * pps, const target_type & x) { - return ppdetail_atomic::print_upto(pps, x); - } - static void print_pretty(ppstate * pps, const target_type & x) { - ppdetail_atomic::print_pretty(pps, x); + static bool print_pretty(const ppindentinfo & ppii, const target_type & x) { + return ppdetail_atomic::print_pretty(ppii, x); } }; template <> struct ppdetail { - static bool print_upto(ppstate * pps, const char * x) { - return ppdetail_atomic::print_upto(pps, x); - } - static void print_pretty(ppstate * pps, const char * x) { - ppdetail_atomic::print_pretty(pps, x); + static bool print_pretty(const ppindentinfo & ppii, const char * x) { + return ppdetail_atomic::print_pretty(ppii, x); } }; -#define PPDETAIL_ATOMIC_BODY(target_type) \ - struct ppdetail { \ - static bool print_upto(ppstate * pps, const target_type & x) { \ - return ppdetail_atomic::print_upto(pps, x); \ - } \ - \ - static void print_pretty(ppstate * pps, const target_type & x) { \ - ppdetail_atomic::print_pretty(pps, x); \ - } \ +#define PPDETAIL_ATOMIC_BODY(target_type) \ + struct ppdetail { \ + static bool print_pretty(const ppindentinfo & ppii, \ + const target_type & x) { \ + return ppdetail_atomic::print_pretty(ppii, x); \ + } \ } -#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \ - struct ppdetail { \ - static bool print_upto(ppstate * pps, const target_type & x) { \ - return ppdetail_atomic::print_upto(pps, x); \ - } \ - \ - static void print_pretty(ppstate * pps, const target_type & x) { \ - ppdetail_atomic::print_pretty(pps, x); \ - } \ +#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \ + struct ppdetail { \ + static bool print_pretty(const ppindentinfo & ppii, \ + const target_type & x) { \ + return ppdetail_atomic::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*/ diff --git a/include/xo/indentlog/print/ppstr.hpp b/include/xo/indentlog/print/ppstr.hpp index 358c14b..f5f2ad5 100644 --- a/include/xo/indentlog/print/ppstr.hpp +++ b/include/xo/indentlog/print/ppstr.hpp @@ -94,44 +94,45 @@ namespace xo { /** implement pretty-printing for template @c ppconcat r**/ template struct ppdetail> { - /** 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 target) { - std::uint32_t saved = pps->pos(); + static bool print_pretty(const ppindentinfo & ppii, ppconcat 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(args)...); + if (std::apply( + [ppii](auto &&... args) { + return detail::ppconcat_printupto_aux(ppii.pps(), std::forward(args)...); + }, + std::forward>(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(args)...); }, std::forward>(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 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(args)...); - }, - std::forward>(target.contents_) - ); } }; } /*namespace print*/ diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index 369713b..c6ed3a4 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -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 #include @@ -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>; - 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 + 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 + 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 + void pretty_struct_members(const ppindentinfo & ppii, + Member && member, + Rest&&... rest); + + /** base-case overload **/ + void pretty_struct_members(const ppindentinfo &) {} + template bool print_upto(T && x); + template + bool print_upto_tag(Name && name, Value && value); + template ppstate& pretty(T && x); + /** write (to internal scratch stream), in order: + * newline, indent to @p ci, tag @p name, space, @p value + **/ + template + ppstate& newline_pretty_tag(std::uint32_t ci, Name && name, Value && value); + /** like pretty(x), but follow with trailing \n **/ template 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 - bool - ppdetail_atomic::print_upto(ppstate * pps, const T & x) + template + 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 - void - ppdetail_atomic::print_pretty(ppstate * pps, const T & x) + template + 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 + 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 bool ppstate::print_upto(T && value) { - return ppdetail>::print_upto(this, value); + std::uint32_t saved = pos(); + + if (!has_margin()) + return false; + + if (!ppdetail>::print_pretty(this->indent_info(true /*upto*/), value)) + return false; + + return scan_no_newline(saved); } template @@ -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>::print_upto(this, static_cast(value)); + /* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ + bool fits = ppdetail>::print_pretty(this->indent_info(true /*upto*/), + static_cast(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>::print_pretty(this, value); + ppdetail>::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>::print_upto(this, value); + /* print_pretty() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ + bool fits = ppdetail>::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>::print_pretty(this, value); + ppdetail>::print_pretty(this->indent_info(false), value); this->newline_indent(0); ppc.check_commit(); return *this; } + template + bool + ppdetail_atomic::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 + 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 + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, + const tag_impl & tag) + { + using tag_type = tag_impl; + + return detail::ppdetail_tag::print_pretty(ppii, tag); + } + }; + + template + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, + const ref_tag_impl & tag) + { + using tag_type = ref_tag_impl; + + return detail::ppdetail_tag::print_pretty(ppii, tag); + } + }; + + template + bool + ppstate::print_upto_tag(Name && name, Value && value) + { + return this->print_upto(xrtag(name, value)); + } + + template + 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*/ diff --git a/include/xo/indentlog/print/pretty_concat.hpp b/include/xo/indentlog/print/pretty_concat.hpp deleted file mode 100644 index 8b48493..0000000 --- a/include/xo/indentlog/print/pretty_concat.hpp +++ /dev/null @@ -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 */ diff --git a/include/xo/indentlog/print/pretty_tag.hpp b/include/xo/indentlog/print/pretty_tag.hpp deleted file mode 100644 index 7402136..0000000 --- a/include/xo/indentlog/print/pretty_tag.hpp +++ /dev/null @@ -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 - struct ppdetail> { - static bool print_upto(ppstate * pps, const tag_impl & 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 & 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 */ diff --git a/include/xo/indentlog/print/pretty_vector.hpp b/include/xo/indentlog/print/pretty_vector.hpp index efccb80..6a05fe1 100644 --- a/include/xo/indentlog/print/pretty_vector.hpp +++ b/include/xo/indentlog/print/pretty_vector.hpp @@ -11,56 +11,47 @@ namespace xo { namespace print { - template - struct ppdetail> { - static bool print_upto(ppstate * pps, const std::vector & x) { - std::uint32_t saved = pps->pos(); - if (!pps->has_margin()) + template + 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 & 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 - struct ppdetail> { - static bool print_upto(ppstate * pps, const std::vector & x) { - return ppdetail>::print_upto(pps, x); - } - static void print_pretty(ppstate * pps, const std::vector & x) { - ppdetail>::print_pretty(pps, x); + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, const std::vector & x) { + return ppdetail_vector>::print_pretty(ppii, x); } }; } diff --git a/include/xo/indentlog/print/quoted.hpp b/include/xo/indentlog/print/quoted.hpp index 6de2bb9..c34d099 100644 --- a/include/xo/indentlog/print/quoted.hpp +++ b/include/xo/indentlog/print/quoted.hpp @@ -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 behavior for completeness' sake: + /** writing out std::forward 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 auto quot(T && x) { return quot_impl(false /*unq_flag*/, std::forward(x)); diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index ad55f3c..008dcbd 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -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(n)}, value_{std::forward(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 + 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(n)}, value_{std::forward(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 - auto //tag_impl> + auto xtag(Name && n, Value && v) { - return tag_impl>(n, v); + return tag_impl, std::decay_t>(n, v); } /*xtag*/ - template - auto //tag_impl> - xtag(char const * n, Value && v) { - return tag_impl>(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 + auto + xrtag(Name && n, Value && v) + { + return tag_impl, std::decay_t>(n, v); + } // ----- tag ----- @@ -102,6 +136,28 @@ namespace xo { return tag_impl(n, v); } + // ----- rtag ----- + + template + auto + rtag(Name && n, Value && v) + { + return tag_impl, std::decay_t>(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 + auto + refrtag(Name && n, Value && v) + { + return ref_tag_impl, std::decay_t>(n, v); + } + // ----- operator<< on tag_impl ----- template @@ -124,6 +180,29 @@ namespace xo { return s; } /*operator<<*/ + + // ----- operator<< on ref_tag_impl ----- + + template + inline std::ostream & + operator<<(std::ostream &s, + ref_tag_impl 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 */ diff --git a/include/xo/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp index 10cabf8..3217e0e 100644 --- a/include/xo/indentlog/scope.hpp +++ b/include/xo/indentlog/scope.hpp @@ -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 #include