From 1277378c9f8e27cbbec90949a3e52cc28d6cbee0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Jun 2025 07:41:44 -0500 Subject: [PATCH 01/42] xo-indentlog: bugfix: parse altered gcc output starting gcc13.2 --- include/xo/indentlog/print/function.hpp | 5 +++-- utest/function.test.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/xo/indentlog/print/function.hpp b/include/xo/indentlog/print/function.hpp index 9a6e56ee..f7827e49 100644 --- a/include/xo/indentlog/print/function.hpp +++ b/include/xo/indentlog/print/function.hpp @@ -97,6 +97,7 @@ namespace xo { std::size_t r = find_toplevel_sep(s4, false /*!last_flag*/); std::string_view s5 = s4.substr(r); /*no namespace qualifier (unless function)*/ + //std::cerr << "print_streamlined: __GNUC__=" << __GNUC__ << ", __GNUC_MINOR__=" << __GNUC_MINOR__ << std::endl; //std::cerr << "print_streamlined: s=[" << s << "]" << std::endl; //std::cerr << "print_streamlined: s2=[" << s2 << "] (excluded [with ..] suffix)" << std::endl; //std::cerr << "print_streamlined: s3=[" << s3 << "] (excluded const suffix)" << std::endl; @@ -147,8 +148,8 @@ namespace xo { /* clang footnote like [CharT = char] instead of [with CharT = char] */ std::size_t p = s.find(" ["); #else -# if (__GNUC__ > 13) || ((__GNUC__ == 13) && (__GNUC_MINOR__ >= 3)) - /* gcc footnote like [CharT = char] instead of [with CharT = char] starting w/ gcc 13.3 (approximately ?)*/ +# if (__GNUC__ > 13) || ((__GNUC__ == 13) && (__GNUC_MINOR__ >= 2)) + /* gcc footnote like [CharT = char] instead of [with CharT = char] starting w/ gcc 13.2 (approximately ?)*/ std::size_t p = s.find(" ["); # else std::size_t p = s.find(" [with "); diff --git a/utest/function.test.cpp b/utest/function.test.cpp index 84b49515..fcb6374e 100644 --- a/utest/function.test.cpp +++ b/utest/function.test.cpp @@ -19,7 +19,7 @@ namespace ut { color_spec_type spec_; /* function signature (as per __PRETTY_FUNCTION__) */ std::string_view pretty_; - /* output text */ + /* output text: expected output from function_name(.style .spec .pretty) */ std::string_view output_; }; /*function_tcase*/ From 991530a10b5b2c4e851130ba4d605e1ea449c6b3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Jun 2025 16:14:08 -0500 Subject: [PATCH 02/42] xo-indentlog: comment nit --- include/xo/indentlog/log_config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/log_config.hpp b/include/xo/indentlog/log_config.hpp index 98a16304..751e62bb 100644 --- a/include/xo/indentlog/log_config.hpp +++ b/include/xo/indentlog/log_config.hpp @@ -8,7 +8,7 @@ #include namespace xo { - /* Tag here b/c we want header-only library */ + /* Tag here because we want header-only library */ template struct log_config_impl { /* display log messages with severity >= .log_level */ From f7a414e0cc070954ffa1b0e6a634f513ef05b688 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 6 Jul 2025 14:13:44 -0500 Subject: [PATCH 03/42] xo-reader xo-expression: nested lambdas working properly + docs --- include/xo/indentlog/log_state.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/log_state.hpp b/include/xo/indentlog/log_state.hpp index 29ad0dda..f188a7cb 100644 --- a/include/xo/indentlog/log_state.hpp +++ b/include/xo/indentlog/log_state.hpp @@ -142,7 +142,7 @@ namespace xo { std::ostream ss_; }; /*state_impl*/ - constexpr uint32_t c_default_buf_size = 1024; + constexpr uint32_t c_default_buf_size = 1024*1024; template state_impl::state_impl() From 28172fc3d68f00d08eb7156f07daffd963ca99f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 13 Jul 2025 21:17:13 -0500 Subject: [PATCH 04/42] xo-indentlog: add general-purposee pretty-printing [WIP] --- CMakeLists.txt | 14 +- example/CMakeLists.txt | 8 +- include/xo/indentlog/log_config.hpp | 12 + include/xo/indentlog/log_state.hpp | 31 +- include/xo/indentlog/log_streambuf.hpp | 129 ++++++-- include/xo/indentlog/print/concat.hpp | 20 ++ include/xo/indentlog/print/hex.hpp | 7 + include/xo/indentlog/print/pad.hpp | 3 + include/xo/indentlog/print/ppconfig.hpp | 34 +++ .../xo/indentlog/print/ppdetail_atomic.hpp | 119 ++++++++ include/xo/indentlog/print/ppstr.hpp | 190 ++++++++++++ include/xo/indentlog/print/pretty.hpp | 275 ++++++++++++++++++ include/xo/indentlog/print/pretty_concat.hpp | 14 + include/xo/indentlog/print/pretty_tag.hpp | 56 ++++ include/xo/indentlog/print/pretty_vector.hpp | 67 +++++ include/xo/indentlog/print/quoted.hpp | 6 + include/xo/indentlog/print/tag.hpp | 102 ++++--- include/xo/indentlog/print/tostr.hpp | 6 +- include/xo/indentlog/print/vector.hpp | 3 +- include/xo/indentlog/scope.hpp | 90 ++++-- utest/CMakeLists.txt | 4 +- utest/log_streambuf.test.cpp | 107 +++++++ utest/pretty_vector.test.cpp | 97 ++++++ utest/toppstr.test.cpp | 55 ++++ 24 files changed, 1327 insertions(+), 122 deletions(-) create mode 100644 include/xo/indentlog/print/ppconfig.hpp create mode 100644 include/xo/indentlog/print/ppdetail_atomic.hpp create mode 100644 include/xo/indentlog/print/ppstr.hpp create mode 100644 include/xo/indentlog/print/pretty.hpp create mode 100644 include/xo/indentlog/print/pretty_concat.hpp create mode 100644 include/xo/indentlog/print/pretty_tag.hpp create mode 100644 include/xo/indentlog/print/pretty_vector.hpp create mode 100644 utest/log_streambuf.test.cpp create mode 100644 utest/pretty_vector.test.cpp create mode 100644 utest/toppstr.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a7ff773..f5958349 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# indentlog/CMakeLists.txt +# xo-indentlog/CMakeLists.txt cmake_minimum_required(VERSION 3.10) @@ -28,12 +28,8 @@ add_subdirectory(utest) set(SELF_LIB indentlog) xo_add_headeronly_library(${SELF_LIB}) xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# docs targets depend on all the other library/utest targets -# -#add_subdirectory(docs) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- @@ -45,4 +41,10 @@ if (XO_ENABLE_EXAMPLES) install(TARGETS indentlog_ex4 DESTINATION bin/indentlog/example) endif() +# ---------------------------------------------------------------- +# docs targets depends on other library/utest/exec targets, +# must come after them +# +add_subdirectory(docs) + # end CMakeLists.txt diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 0f629f7a..72eaa8b9 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,9 +1,7 @@ -set(PROJECT_CXX_FLAGS "--std=c++20") -add_definitions(${PROJECT_CXX_FLAGS}) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +#set(CMAKE_CXX_STANDARD 20) +#set(CMAKE_CXX_STANDARD_REQUIRED True) +#set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") #include(cmake/FindSphinx.cmake) diff --git a/include/xo/indentlog/log_config.hpp b/include/xo/indentlog/log_config.hpp index 751e62bb..198d36a6 100644 --- a/include/xo/indentlog/log_config.hpp +++ b/include/xo/indentlog/log_config.hpp @@ -19,6 +19,10 @@ namespace xo { static bool time_local_flag; /* true to log time-of-day with microsecond precision; false for millisecond precision */ static bool time_usec_flag; + /* true to enable pretty-printing (see basic_scope::log()) */ + static bool pretty_print_enabled; + /* when pretty-printing enabled, use newlines in effort to avoid writing beyond this margin */ + static std::uint32_t right_margin; /* spaces per nesting level. 0 -> no indenting */ static std::uint32_t indent_width; /* max #of spaces to introduce when indenting */ @@ -58,6 +62,14 @@ namespace xo { bool log_config_impl::time_usec_flag = true; + template + bool + log_config_impl::pretty_print_enabled = true; + + template + std::uint32_t + log_config_impl::right_margin = 130; + template std::uint32_t log_config_impl::indent_width = 2; diff --git a/include/xo/indentlog/log_state.hpp b/include/xo/indentlog/log_state.hpp index f188a7cb..e28fb2c4 100644 --- a/include/xo/indentlog/log_state.hpp +++ b/include/xo/indentlog/log_state.hpp @@ -35,6 +35,7 @@ namespace xo { void decr_nesting() { --nesting_level_; } std::ostream & ss() { return ss_; } + log_streambuf_type & sbuf() { return *p_sbuf_phase1_.get(); } void check_print_time(utc_nanos now_tm) { using xo::time::timeutil; @@ -116,12 +117,6 @@ namespace xo { */ std::unique_ptr p_sbuf_phase1_; - /* #of characters found in .p_sbuf_phase1 since last \n. - * this value is established+updated in .flush2sbuf(). - * (in particular ignored by stream .ss()) - */ - std::size_t lpos_ = 0; - /* whenever .set_location() is called: * - capture (file, line) * - print them near right margin with next output line @@ -256,9 +251,14 @@ namespace xo { state_impl::flush2sbuf(std::streambuf * p_sbuf) { log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get(); + + // instead of post-processing, rely on newline-aware log_streambuf + // to indent in advance + +#ifdef OBSOLETE log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get(); - /* generally expecting sbuf to contain one line of output. + /* often sbuf contains one line of output. * if it contains multiple newlines, need to indent * after each one. * @@ -269,8 +269,8 @@ namespace xo { * in the unlikely event that it's non-zero */ char const * s = sbuf1->lo(); - char const * e = s + sbuf1->pos(); + char const * e = s + sbuf1->pos(); char const * p = s; /* point to first space following a non-space character. @@ -290,8 +290,14 @@ namespace xo { /* for indenting, looking for first 'space following non-space, on first line', if any */ - std::size_t lpos_on_newline = 0; +#ifdef OBSOLETE + // ..multiline input should have already been indented by custom log_streambuf. + // may need to extend to recognize terminal control sequences like below + std::size_t lpos_on_newline = 0; +#endif + +#ifdef OBSOLETE while(p < e) { if(space_after_nonspace) { ; @@ -318,8 +324,10 @@ namespace xo { in_color_escape = false; +#ifdef OBSOLETE lpos_on_newline = this->lpos_; this->lpos_ = 0; +#endif ++p; break; @@ -329,6 +337,7 @@ namespace xo { ++p; } } +#endif /* p=e or *p=\n */ @@ -382,9 +391,11 @@ namespace xo { n_indent += std::min(this->nesting_level_ * log_config::indent_width, log_config::max_indent_width); +#ifdef OBSOLETE // nice try, broken for multiline input + written before log_streambuf calculated lpos /* this is just to indent for per-line entry/exit label */ if(space_after_nonspace) n_indent += (space_after_nonspace - s); +#endif for(std::uint32_t i = 0; i < n_indent; ++i) sbuf2->sputc(' '); @@ -394,6 +405,8 @@ namespace xo { /* now write entire contents of *sbuf2 to clog */ p_sbuf->sputn(sbuf2->lo(), sbuf2->pos()); +#endif + p_sbuf->sputn(sbuf1->lo(), sbuf1->pos()); /* reset streams for next message */ this->reset_stream(); diff --git a/include/xo/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp index 1a761655..855fc05b 100644 --- a/include/xo/indentlog/log_streambuf.hpp +++ b/include/xo/indentlog/log_streambuf.hpp @@ -2,6 +2,7 @@ #pragma once +#include "print/quoted_char.hpp" #include #include #include // e.g. for std::memcpy() @@ -9,13 +10,14 @@ #include namespace xo { - /* recycling buffer for logging. - * write to self-extending storage array; - */ - template + /** recycling buffer for logging and pretty-printing + * - write to self-extending storage array + * - track position relative to start of line + **/ + template > class log_streambuf : public std::streambuf { public: - log_streambuf(std::uint32_t buf_z) { + log_streambuf(std::uint32_t buf_z, bool debug_flag = false) : debug_flag_{debug_flag} { this->buf_v_.resize(buf_z); this->reset_stream(); } /*ctor*/ @@ -25,40 +27,93 @@ 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_; } + + bool debug_flag() const { return debug_flag_; } + void reset_stream() { char * p_lo = &(this->buf_v_[0]); char * p_hi = p_lo + this->capacity(); /* tells parent our buffer extent */ this->setp(p_lo, p_hi); + + this->solpos_ = 0; } /*reset_stream*/ + void rewind_to(std::uint32_t p) { + /* .setp(): using for side effect: sets .pptr to .pbase */ + this->setp(this->pbase(), this->epptr()); + this->pbump(p); + } + protected: + /** expand buffer storage (by 2x), preserve current contents **/ + void + expand_to(std::size_t new_z) { + char * old_pptr = pptr(); + std::streamsize old_n = old_pptr - pbase(); + + assert(old_n <= static_cast(buf_v_.size())); + assert(new_z > buf_v_.capacity()); + + this->buf_v_.resize(new_z); + + char * p_base = &(this->buf_v_[0]); + char * p_hi = p_base + this->buf_v_.capacity(); + + this->setp(p_base, p_hi); + this->pbump(old_n); + } /*expand*/ + virtual std::streamsize xsputn(char const * s, std::streamsize n) override { /* s must be an address in [this->lo() .. this->lo() + capacity()] */ - assert(this->hi() >= this->pptr()); + assert(hi() >= pptr()); -#ifdef NOT_USING_DEBUG - std::cout << "xsputn: pbase=" << (void *)(this->pbase()) - << ", pptr=" << (void*)(this->pptr()) - << "(+" << (this->pptr() - this->lo()) << ")" - << ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")" - << ", buf_v.size=" << this->buf_v_.size() - << std::endl; -#endif - //std::cout << "xsputn: s=" << quot(string_view(s, n)) << ", n=" << n << std::endl; + if (pptr() + n > hi()) { + this->expand_to(std::max(2 * this->buf_v_.capacity(), std::size_t(this->pos() + n + 1))); + } + + if (debug_flag_) { + std::cout << "xsputn: pbase=" << (void *)(this->pbase()) + << ", pptr=" << (void*)(this->pptr()) + << "(+" << (this->pptr() - this->lo()) << ")" + << ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")" + << ", buf_v.size=" << this->buf_v_.size() + << std::endl; + } + + std::streamsize ncopied = 0; if (this->pptr() + n > this->hi()) { - n = this->hi() - this->pptr(); - std::memcpy(this->pptr(), s, n); + ncopied = this->hi() - this->pptr(); } else { - std::memcpy(this->pptr(), s, n); + ncopied = n; } - this->pbump(n); - return n; + if (debug_flag_) { + std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)" + << ", lo=" << (void*)this->pptr() + << ", hi=" << (void*)(this->pptr() + n) + << std::endl; + } + + 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) { + if (*p == '\n' || *p == '\r') { + this->solpos_ = (p+1 - this->pbase()); + break; + } + } + + this->pbump(ncopied); + + return ncopied; } /*xsputn*/ virtual int_type @@ -69,25 +124,31 @@ namespace xo { assert(old_n <= static_cast(this->buf_v_.size())); - std::size_t new_z = 2 * this->buf_v_.size(); + if (debug_flag_) { + std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl; + } + + this->expand_to(2 * buf_v_.size()); - this->buf_v_.resize(new_z); this->buf_v_[old_n] = new_ch; + this->pbump(1); - /* 'buffered range' will now be .buf_v[old_n .. new_z] */ - char * p_base = &(this->buf_v_[0]); - //char * p_lo = &(this->buf_v_[old_n+1]); - char * p_hi = p_base + this->buf_v_.capacity(); + if ((new_ch == static_cast('\n')) || (new_ch == static_cast('\r'))) + this->solpos_ = this->pos(); - this->setp(p_base, p_hi); - this->pbump(old_n + 1); /*see 'this->buf_v_[old_n] - new_ch' above*/ - - return new_ch; + 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. * which. in|out|both + * + * Note that off=0,dir=cur,which=out reads offset */ virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir, @@ -114,8 +175,14 @@ namespace xo { } /*seekoff*/ private: - /* buffered output stored here */ + /** position (relative to pbase) one character after last \n or \r. + * Use to drive @ref lpos + **/ + std::size_t solpos_ = 0; + /** buffered output stored here **/ std::vector buf_v_; + /** true to debug log_streambuf itself **/ + bool debug_flag_ = false; }; /*log_streambuf*/ } /*namespace xo*/ diff --git a/include/xo/indentlog/print/concat.hpp b/include/xo/indentlog/print/concat.hpp index 223900ec..3c166c41 100644 --- a/include/xo/indentlog/print/concat.hpp +++ b/include/xo/indentlog/print/concat.hpp @@ -2,6 +2,7 @@ #pragma once +#include "ppdetail_atomic.hpp" #include #include // for std::move() @@ -37,6 +38,25 @@ namespace xo { 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 = 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); + } + }; + } +#endif } /*namespace xo*/ /* end concat.hpp */ diff --git a/include/xo/indentlog/print/hex.hpp b/include/xo/indentlog/print/hex.hpp index 8a6517d5..4fbf49fa 100644 --- a/include/xo/indentlog/print/hex.hpp +++ b/include/xo/indentlog/print/hex.hpp @@ -2,6 +2,7 @@ #pragma once +#include "ppdetail_atomic.hpp" #include #include @@ -140,6 +141,12 @@ namespace xo { return os; } +#ifndef ppdetail_atomic + namespace print { + PPDETAIL_ATOMIC(hex_view); + } +#endif + } /*namespace xo*/ /* end hex.hpp */ diff --git a/include/xo/indentlog/print/pad.hpp b/include/xo/indentlog/print/pad.hpp index e85d9c25..43aa7e6f 100644 --- a/include/xo/indentlog/print/pad.hpp +++ b/include/xo/indentlog/print/pad.hpp @@ -38,6 +38,9 @@ namespace xo { inline pad_impl pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); } + inline pad_impl + spaces(std::uint32_t n) { return pad_impl(n, ' '); } + inline std::ostream & operator<<(std::ostream & s, pad_impl const & pad) diff --git a/include/xo/indentlog/print/ppconfig.hpp b/include/xo/indentlog/print/ppconfig.hpp new file mode 100644 index 00000000..1cc60597 --- /dev/null +++ b/include/xo/indentlog/print/ppconfig.hpp @@ -0,0 +1,34 @@ +/* @file ppconfig.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include + +namespace xo { + namespace print { + + /** @class ppconfig + * @brief hold pretty-printer control parameters + * + * Need one read-only instance of this to invoke pretty printer + **/ + struct ppconfig { + /** @defgroup ppconfig-instance-vars ppconfig instance variables **/ + ///@{ + + /** max line length. + * Pretty-printer will introduce newlines if needed + * to stay to the left of this margin + **/ + std::uint32_t right_margin_ = 135; + + /** amount of additional indent per nesting level **/ + std::uint32_t indent_width_ = 2; + + ///@} + }; + } /*namespace print*/ +} /*namespace xo*/ diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp new file mode 100644 index 00000000..05614eed --- /dev/null +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -0,0 +1,119 @@ +/* ppdetail_atomic.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include +#include + +namespace xo { + namespace print { + struct ppstate; // see pretty.hpp + +// 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 + +#define ppdetail_atomic ppdetail + + /** @class ppdetail + * @brief template for opt-in to pretty-printer + * + * Provide a specialization that covers each type you want participating in pretty-printing. + * Default treats T as atomic. + * + * Expectations for specializers: + * - don't print final newline: + * - parent container will do that + * - want option to put closing ),],>,} on the same line + **/ + template + struct ppdetail; + + template + struct ppdetail_atomic; + + template + struct ppdetail_atomic { + /** 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_ + **/ + 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); + }; + +#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); + } + }; + + 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); + } + }; + + +#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_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(target_type) \ + template<> \ + PPDETAIL_ATOMIC_BODY(target_type) + +#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(std::string); + + using voidptr = void*; + PPDETAIL_ATOMIC(voidptr); +#endif + + } +} diff --git a/include/xo/indentlog/print/ppstr.hpp b/include/xo/indentlog/print/ppstr.hpp new file mode 100644 index 00000000..358c14b6 --- /dev/null +++ b/include/xo/indentlog/print/ppstr.hpp @@ -0,0 +1,190 @@ +/* file ppstr.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include "xo/indentlog/print/pretty.hpp" +#include "xo/indentlog/print/pretty_vector.hpp" +#include +#include +#include + +namespace xo { + + /** @class ppconcat + * @brief Container for a tuple of values to be pretty-printed in order + * @tparam Args... parameter pack of argument types + * + * We don't use the tuple directly, so that we don't inadvertently + * usurp pretty-printing behavior for all tuples. + * + **/ + template + struct ppconcat { + /** tuple to be pretty-printed. See toppstr(), toppstr2() **/ + std::tuple contents_; + }; + + namespace print { + namespace detail { + inline bool + ppconcat_printupto_aux(print::ppstate *) { + return true; + } + + template + bool + ppconcat_printupto_aux(print::ppstate * pps, T && x) { + if (!pps->print_upto(x)) + return false; + if (!pps->has_margin()) + return false; + return true; + } + + template + bool + ppconcat_printupto_aux(print::ppstate * pps, T && x, Tn && ...rest) { + if (!pps->print_upto(x)) + return false; + if (!pps->has_margin()) + return false; + return ppconcat_printupto_aux(pps, rest...); + } + + inline void + ppconcat_print_pretty_rest(print::ppstate *, std::uint32_t) {} + + template + void + ppconcat_print_pretty_rest(print::ppstate * pps, std::uint32_t ci1, T && x) { + pps->newline_indent(ci1); + pps->pretty(x); + } + + template + void + ppconcat_print_pretty_rest(print::ppstate * pps, std::uint32_t ci1, T && x, Tn && ...rest) { + pps->newline_indent(ci1); + pps->pretty(x); + + ppconcat_print_pretty_rest(pps, ci1, rest...); + } + + inline void + ppconcat_print_pretty_aux(print::ppstate *, std::uint32_t /*ci1*/) {} + + template + void + ppconcat_print_pretty_aux(print::ppstate * pps, std::uint32_t ci1, T && x, Tn && ...rest) { + // first item doesn't get extra indent. Want pretty output like: + // + // pretty(first) + // pretty(second) + // pretty(third) + // .. + // + pps->pretty(x); + ppconcat_print_pretty_rest(pps, ci1, rest...); + } + } + + /** implement pretty-printing for template @c ppconcat r**/ + template + struct ppdetail> { + /** 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. + **/ + static bool print_upto(ppstate * pps, ppconcat target) { + std::uint32_t saved = pps->pos(); + + if (!pps->has_margin()) + return false; + + if (std::apply( + [pps](auto &&... args) { + return detail::ppconcat_printupto_aux(pps, 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*/ + + /** @return string comprised of pretty-printed sequence of values in @p args + * + * Example: + * @code + * toppstr("hello") -> "hello" + * toppstr("string1", .., "string99") + * -> "string1 + * string2 + * ... + * string99" + * @endcode + **/ + template + std::string toppstr(Tn&&... args) { + print::ppconfig ppc; + + std::stringstream ss; + print::ppstate_standalone pps(&ss, 0, &ppc); + + // - std::decay_t remove reference and cv-qualifiers + // - brace initialization of ppconcat and its contained tuple + pps.pretty(ppconcat...>{{std::forward(args)...}}); + + return ss.str(); + } + + /** like @ref toppstr, but use pretty-printing configuration @p ppc + * + * Example: + * @code + * ppconfig ppc{.right_margin_=20, .indent_width_=4}; + * toppstr2("string1", "string2", "string3") -> + * "string1 + * string2 + * string3" + * @endcode + **/ + template + std::string toppstr2(const print::ppconfig& ppc, Tn&&... args) { + std::stringstream ss; + print::ppstate_standalone pps(&ss, 0, &ppc); + + // - std::decay_t remove reference and cv-qualifiers + // - brace initialization of ppconcat and its contained tuple + pps.pretty(ppconcat...>{{std::forward(args)...}}); + + return ss.str(); + } + +} /*namespace xo*/ + +/* end ppstr.hpp */ diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp new file mode 100644 index 00000000..369713b0 --- /dev/null +++ b/include/xo/indentlog/print/pretty.hpp @@ -0,0 +1,275 @@ +/* file pretty.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include "xo/indentlog/print/ppconfig.hpp" +#include "xo/indentlog/log_streambuf.hpp" +#include "ppdetail_atomic.hpp" +#include "pad.hpp" +#include +#include +#include +#include + +namespace xo { + namespace print { + + /** @class ppstate + * @brief hold pretty-printer state + * + * Use: + * + * ppconfig ppc; + * ppstate pps(&cout, 0, &ppc); + * + * pps.pretty("first"); + * pps.pretty("second"); + **/ + struct ppstate { + using streambuf_type = log_streambuf>; + + 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_; } + + std::uint32_t pos() const { return scratch_sbuf_->pos(); } + std::uint32_t lpos() const { return scratch_sbuf_->lpos(); } + + /** space available from current position until @c ppconfig.right_margin_ + * 0 if current position is already beyond right margin + **/ + std::uint32_t avail_margin() const { + std::uint32_t p = lpos(); + std::uint32_t m = config_->right_margin_; + + return (p < m) ? (m - p) : 0; + } + + bool has_margin() const { return avail_margin() > 0; } + + /** true if at least @p budget chars of space available + * before reaching right margin @c ppconfig.right_margin_ + **/ + bool has_budget(std::uint32_t budget) { + return avail_margin() >= budget; + } + + /** true if no newlines in range [@p lo, pos), + * where pos is current position + **/ + bool scan_no_newline(std::uint32_t start) const { + char const * p = scratch_sbuf_->lo() + start; + char const * e = scratch_sbuf_->lo() + pos(); + + for (; p < e; ++p) { + if (*p == '\n') + return false; + } + + return true; + } + + void indent(std::uint32_t tab) { + (*scratch_ss_) << spaces(tab); + } + + void newline_indent(std::uint32_t tab) { + (*scratch_ss_) << "\n"; + this->indent(tab); + } + + template + void write(T && x) { + (*scratch_ss_) << x; + } + + uint32_t incr_nesting_level() { return ++nesting_level_; } + uint32_t decr_nesting_level() { return --nesting_level_; } + + virtual void commit() {} + + template + bool print_upto(T && x); + + template + ppstate& pretty(T && x); + + /** like pretty(x), but follow with trailing \n **/ + template + ppstate& prettyn(T && x); + + private: + /** scratch space for pretty-printed output. + * Shared across nesting pretty calls. + * Caller responsible for saving/restoring state. + **/ + streambuf_type * scratch_sbuf_ = nullptr; + + /** ostream pointing to @ref scratch_sbuf_ **/ + std::ostream * scratch_ss_ = nullptr; + + /** level of reentrant pretty calls. + * Collect formatted text in @ref scratch_sbuf_ + * until end of toplevel @ref pretty call + **/ + std::uint32_t nesting_level_ = 0; + + /** current desired indent from left margin **/ + std::uint32_t current_indent_ = 0; + + /** readonly pretty-printer config **/ + const ppconfig * config_ = nullptr; + }; + + /** @class ppstate_standalone + * @brief like ppstate, but also holds streambuf + */ + struct ppstate_standalone : public ppstate { + explicit ppstate_standalone(std::ostream * os, std::uint32_t ci, const ppconfig * config) + : ppstate(ci, config, &scratch_ss_, &scratch_sbuf_), output_{os}, scratch_sbuf_{1024*1024} + {} + + virtual void commit() override { + output_->write(scratch_sbuf_.lo(), scratch_sbuf_.pos()); + reset_scratch_sbuf(); + } + + void reset_scratch_sbuf() { + /* reset scratch_sbuf for next time */ + scratch_sbuf_.pubseekoff(0, std::ios_base::beg, std::ios_base::out); + } + + /** send final output to this stream **/ + std::ostream * output_ = nullptr; + + /** streambuf (with newline tracking) **/ + streambuf_type scratch_sbuf_; + + /** always attached to @ref scratch_sbuf_ **/ + std::ostream scratch_ss_{&scratch_sbuf_}; + }; + + struct ppcommitter { + ppcommitter(ppstate * pps) : pps_{pps} { + pps_->incr_nesting_level(); + } + ~ppcommitter() { + check_commit(); + } + + void check_commit() { + if (pps_) { + ppstate * pps = pps_; + pps_ = nullptr; + if (pps->decr_nesting_level() == 0) + pps->commit(); + } + } + + ppstate * pps_ = nullptr; + }; + + template + bool + ppdetail_atomic::print_upto(ppstate * pps, const T & x) + { + /* position before we write */ + if (!pps->has_margin()) + 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) + { + /* In default implementation we don't know where to introduce newlines. + * Still need to calculate lpos though + */ + pps->write(x); + } + + template + bool + ppstate::print_upto(T && value) { + return ppdetail>::print_upto(this, value); + } + + template + ppstate & + ppstate::pretty(T && value) { + /* just because value fits doesn't mean we should print it. + * Decision depends on ancestor context + */ + + std::uint32_t saved = pos(); + + ppcommitter ppc(this); + + /* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ + bool fits = ppdetail>::print_upto(this, static_cast(value)); + + if (fits) { + /* fits on 1 line -> pretty-printing maybe not required */ + return *this; + } + + /* here: didn't fit -> split over multiple lines */ + + this->scratch_sbuf_->rewind_to(saved); + ppdetail>::print_pretty(this, value); + ppc.check_commit(); + + return *this; + } + + template + ppstate & + ppstate::prettyn(T && value) { + /* just because value fits doesn't mean we should print it. + * Decision depends on ancestor context + */ + + std::uint32_t saved = pos(); + + ppcommitter ppc(this); + + /* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */ + bool fits = ppdetail>::print_upto(this, value); + + if (fits) { + this->newline_indent(0); + /* fits on 1 line -> pretty-printing maybe not required */ + return *this; + } + + /* here: didn't fit -> split over multiple lines */ + + this->scratch_sbuf_->rewind_to(saved); + ppdetail>::print_pretty(this, value); + this->newline_indent(0); + ppc.check_commit(); + + return *this; + } + + } /*namespace print*/ +} /*namespace xo*/ diff --git a/include/xo/indentlog/print/pretty_concat.hpp b/include/xo/indentlog/print/pretty_concat.hpp new file mode 100644 index 00000000..8b484936 --- /dev/null +++ b/include/xo/indentlog/print/pretty_concat.hpp @@ -0,0 +1,14 @@ +/* @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 new file mode 100644 index 00000000..7402136b --- /dev/null +++ b/include/xo/indentlog/print/pretty_tag.hpp @@ -0,0 +1,56 @@ +/* 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 new file mode 100644 index 00000000..efccb800 --- /dev/null +++ b/include/xo/indentlog/print/pretty_vector.hpp @@ -0,0 +1,67 @@ +/* file pretty_vector.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include "pretty.hpp" +#include "pad.hpp" +#include + +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()) + 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); + } + }; + } +} diff --git a/include/xo/indentlog/print/quoted.hpp b/include/xo/indentlog/print/quoted.hpp index 16e66267..6de2bb95 100644 --- a/include/xo/indentlog/print/quoted.hpp +++ b/include/xo/indentlog/print/quoted.hpp @@ -5,6 +5,7 @@ #pragma once +#include "ppdetail_atomic.hpp" #include "tostr.hpp" #include #include @@ -147,6 +148,11 @@ namespace xo { auto unq(T && x) { return quot_impl(true /*unq_flag*/, std::forward(x)); } + +#ifndef ppdetail_atomic + template + PPDETAIL_ATOMIC_BODY(quot_impl); +#endif } /*namespace print*/ } /*namespace xo*/ diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index 0973506d..ad55f3c2 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -18,18 +18,45 @@ #define TAG2(x, y) xo::make_tag(x, y) #define XTAG(x) xo::xtag(STRINGIFY(x), x) -//#define XTAG2(x, y) xo::xtag(x, y) namespace xo { - // associate a name with a value - // - // will print like - // :name value - // - // NOTE: will search for operator<< overloads in the logutil - // namespace - //*/ - template + enum class tagstyle { + /** print with automatic escapes for embedded special characters + * (any of ' ','"','\','\n','\r','\t'). + * print inside double-quotes if an escape is required. + **/ + autoescape, + /** print literally. caller responsible for machine-readability **/ + raw, + }; + + /** K,V pair for printing. + * + * @tparam PrefixSpace if true print one space before :K + * @tparam TagStyle controls printing format + * @tparam Name typename for key K. + * @tparam Value typename for value V. + * + * 1. Uses escapes to preserve machine-readability of V. + * 2. Optionally colors K for readability. + * Can disable with + * @code + * xo::tag_config::tag_color = xo::color_spec_type::none() + * @endcode + * + * Printing styles + * @pre + * :name value // unadorned + * :name "value needing\nescapes" // with escapes + * :name // autoescape disabled + * @endpre + * + * NOTE: will search for operator<< overloads in the xo + * namespace + * + * + **/ + template struct tag_impl { tag_impl(Name const & n, Value const & v) : name_{n}, value_{v} {} @@ -42,71 +69,58 @@ namespace xo { private: Name name_; Value value_; - }; /*tag_impl*/ + }; - /* deduce tag template-type from arguments */ - template - tag_impl - make_tag(Name && n, Value && v) - { - return tag_impl(n, v); - } /*make_tag*/ - - template - tag_impl - make_tag(char const * n, Value && v) { - return tag_impl(n, v); - } /*make_tag*/ + // ----- xtag ----- template - tag_impl + auto //tag_impl> xtag(Name && n, Value && v) { - return tag_impl(n, v); + return tag_impl>(n, v); } /*xtag*/ template - tag_impl + auto //tag_impl> xtag(char const * n, Value && v) { - return tag_impl(n, v); + return tag_impl>(n, v); } /*xtag*/ - inline - tag_impl - xtag_pre(char const * n) { - return tag_impl(n, ""); - } /*xtag_pre*/ - // ----- tag ----- template - tag_impl + tag_impl tag(Name && n, Value && v) { - return tag_impl(n, v); - } /*tag*/ + return tag_impl(n, v); + } template - tag_impl + tag_impl tag(char const * n, Value && v) { - return tag_impl(n, v); - } /*tag*/ + return tag_impl(n, v); + } // ----- operator<< on tag_impl ----- - template + template inline std::ostream & operator<<(std::ostream &s, - tag_impl const & tag) + tag_impl const & tag) { using xo::print::unq; - if(PrefixSpace) + if (PrefixSpace) s << " "; s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) - << " " << unq(tag.value()); + << " "; + + if (TagStyle == tagstyle::autoescape) + s << unq(tag.value()); + else + s << tag.value(); return s; } /*operator<<*/ diff --git a/include/xo/indentlog/print/tostr.hpp b/include/xo/indentlog/print/tostr.hpp index e395cfe3..ca50e3a3 100644 --- a/include/xo/indentlog/print/tostr.hpp +++ b/include/xo/indentlog/print/tostr.hpp @@ -60,7 +60,7 @@ namespace xo { */ /* no-op terminal case */ - template + template Stream & tos(Stream & s) { return s; } @@ -70,14 +70,14 @@ namespace xo { // is the same as // s << a << b << c; // - template + template Stream & tos(Stream & s, T && x) { s << x; return s; } /*tos*/ template - Stream &tos(Stream &s, T &&x, Tn &&...rest) { + Stream &tos(Stream & s, T && x, Tn && ...rest) { s << x; return tos(s, rest...); } /*tos*/ diff --git a/include/xo/indentlog/print/vector.hpp b/include/xo/indentlog/print/vector.hpp index be5f4497..5e877547 100644 --- a/include/xo/indentlog/print/vector.hpp +++ b/include/xo/indentlog/print/vector.hpp @@ -7,12 +7,13 @@ #include #include +#include namespace std { template inline std::ostream & operator<<(std::ostream & os, - std::vector const & v) + const std::vector & v) { os << "["; for(size_t i=0, z=v.size(); i #include @@ -31,15 +34,18 @@ namespace xo { /* throw exception if condition not met*/ # define XO_EXPECT(f,msg) if(!(f)) { throw std::runtime_error(msg); } /* establish scope using current function name */ -# define XO_SCOPE(name, lvl) xo::scope name(xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) +# define XO_SCOPE(varname, lvl) xo::scope varname(xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) /* like XO_SCOPE(name), but also set enabled flag */ //# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag)) # define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_level::never, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } - /* convenience class for basic_scope<..> construction (see below). - * use to disambiguate setup from other arguments - */ + /** @class scope_setup + * @brief Collect code-location information. + * + * Typically used with logging macros like @ref XO_SCOPE + * Application code isn't expected to interact with this class directly + **/ struct scope_setup { scope_setup(log_level level, function_style style, std::string_view name1, std::string_view name2, std::string_view file, std::uint32_t line) @@ -53,21 +59,33 @@ namespace xo { //static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); } //static scope_setup literal(std::string_view name1, std::string_view name2, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, name2, enabled_flag); } - /* threshold level for logging -- write messages with severity >= this level */ + /** @defgroup scope_setup-instance-vars scope_setup instance variables **/ + ///@{ + + /** minimum severity level for logging -- write messages with severity >= this level **/ log_level log_level_ = log_level::error; - /* FS_Pretty | FS_Streamlined | FS_Simple */ + /** @c FS_Pretty | @c FS_Streamlined | @c FS_Simple **/ function_style style_ = function_style::pretty; + /** name extracted from left-hand side of symbol split (e.g. foo in method foo::bar) **/ std::string_view name1_ = "<.name1>"; + /** name extracted from right-hand side of symbol split (e.g. bar in method foo::bar) **/ std::string_view name2_ = "<.name2>"; - /* __FILE__ */ + /** captured value of `__FILE__` **/ std::string_view file_ = "<.file>"; - /* __LINE__ */ + /** captured value of `__LINE__` **/ std::uint32_t line_ = 0; + + ///@} }; /*scope_setup*/ - /* nesting logger + /** @class basic_scope + * @brief Track nesting level across participating function calls use to drive indentation. * - * Use: + * @tparam CharT character representation type. Usually @c char + * @tparam Traits character traits, usually @c std::char_traits + * + * Example: + * @code * using xo::scope; * * void myfunc() { @@ -78,7 +96,7 @@ namespace xo { * } * * void anotherfunc() { - * XO_SCOPE(x); // or scope x("anotherfunc") + * XO_SCOPE(x); // or scope x("anotherfunc"). * x.log(y); * } * @@ -98,14 +116,15 @@ namespace xo { * -anotherfunc: * d,e,f * -myfunc: - */ + * @endcode + **/ template > class basic_scope { public: using state_impl_type = state_impl; + using log_streambuf_type = log_streambuf; public: - //basic_scope(std::string_view name1, bool enabled_flag); template basic_scope(scope_setup setup, Tn&&... rest); ~basic_scope(); @@ -119,6 +138,7 @@ namespace xo { void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; } + /** Log arguments in pack @p rest **/ template bool log(Tn&&... rest) { if(this->finalized_) { @@ -129,8 +149,23 @@ namespace xo { /* indent for timestamp (not printed on this line) */ logstate->time_indent(); - /* log to per-thread stream to prevent data races */ - tosn(logstate2stream(logstate), std::forward(rest)...); + if (log_config::pretty_print_enabled) { + print::ppconfig ppc = { + .right_margin_ = log_config::right_margin, + .indent_width_ = log_config::indent_width + }; + std::ostream& ss = logstate2stream(logstate); + log_streambuf_type & sbuf = logstate2streambuf(logstate); + + /* use 0 for indent because flush2sbuf responsible for basic_scope toplevel indent */ + print::ppstate pps(0 /*ci*/, &ppc, &ss, &sbuf); + + pps.prettyn(ppconcat...>{{std::forward(rest)...}}); + + } else { + /* log to per-thread stream to prevent data races */ + tosn(logstate2stream(logstate), std::forward(rest)...); + } this->flush2sbuf(logstate); } @@ -138,18 +173,21 @@ namespace xo { return true; } /*log*/ + /** Log argument in pack @p args **/ template bool operator()(Tn&&... args) { return this->log(std::forward(args)...); } - /* call once to end scope before dtor */ + /** Optionally, call once to end scope before dtor. + * Logs arguments in pack @p args + **/ template void end_scope(Tn&&... args); private: - /* establish stream for logging; use thread-local storage for threadsafetỵ - * stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread), - * will be in 'reset-to-beginning of buffer' statẹ - */ + /** establish stream for logging; use thread-local storage for threadsafetỵ + * stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread), + * will be in 'reset-to-beginning of buffer' statẹ + **/ static state_impl_type * require_indent_thread_local_state(); /* establish logging state; use thread-local storage for threadsafety */ @@ -158,6 +196,9 @@ namespace xo { /* retrieve permanently-associated ostream for logging-state */ static std::ostream & logstate2stream(state_impl_type * logstate); + /* retreive permanently-associated streambuf for logging-state */ + static log_streambuf_type & logstate2streambuf(state_impl_type * logstate); + /* write collected output to std::clog, or chosen streambuf */ void flush2sbuf(state_impl_type * logstate); @@ -261,6 +302,13 @@ namespace xo { return logstate->ss(); } /*logstate2stream*/ + template + log_streambuf & + basic_scope::logstate2streambuf(state_impl_type * logstate) + { + return logstate->sbuf(); + } + template void basic_scope::flush2sbuf(state_impl_type * logstate) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index ea4bb819..c8ea729c 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,8 +3,8 @@ set(SELF_EXECUTABLE_NAME utest.indentlog) set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp timeutil.test.cpp tag.test.cpp - filename.test.cpp code_location.test.cpp function.test.cpp - indentlog_utest_main.cpp) + filename.test.cpp code_location.test.cpp function.test.cpp pretty_vector.test.cpp + indentlog_utest_main.cpp log_streambuf.test.cpp toppstr.test.cpp) xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp new file mode 100644 index 00000000..e145bd96 --- /dev/null +++ b/utest/log_streambuf.test.cpp @@ -0,0 +1,107 @@ +/* @file log_streambuf.test.cpp */ + +#include "xo/indentlog/log_streambuf.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/quoted.hpp" +#include +//#include + +namespace ut { + using xo::log_streambuf; + + TEST_CASE("log_streamhuf", "[log_streambuf]") { + std::size_t z = 16; + log_streambuf> sbuf(z); + std::ostream ss(&sbuf); + + REQUIRE(sbuf.capacity() == z); + REQUIRE(sbuf.pos() == 0); + REQUIRE(sbuf.lpos() == 0); + + ss << '\n'; + + REQUIRE(sbuf.capacity() == z); + REQUIRE(sbuf.pos() == 1); + REQUIRE(sbuf.lpos() == 1); + } + + // write test cases with some random strings. + // for each string a list of tuples {ch,pos,lpos} + + struct recd { + char const * text_ = nullptr; + std::size_t expect_pos_ = 0; + std::size_t expect_lpos_ = 0; + }; + + struct test_case { + std::size_t buf_capacity_; + std::vector steps_; + }; + + std::vector s_testcase_v = { + {64, {{"\n", 1, 0}, + {"abcd", 5, 4}, + {"abcd\nefg\nhij", 17, 3}, + {"klmnopqrstuvwxyz", 33, 19}, + }}, + {32, {{"\n", 1, 0}, + {"abcd", 5, 4}, + {"abcd\nefg\nhij", 17, 3}, + {"klmnopqrstuvwxyz", 33, 19}, + }}, + {16, {{"\n", 1, 0}, + {"abcd", 5, 4}, + {"abcd\nefg\nhij", 17, 3}, + {"klmnopqrstuvwxyz", 33, 19}, + }}, + {8, {{"\n", 1, 0}, + {"abcd", 5, 4}, + {"abcd\nefg\nhij", 17, 3}, + {"klmnopqrstuvwxyz", 33, 19}, + }}, + {4, {{"\n", 1, 0}, + {"abcd", 5, 4}, + {"abcd\nefg\nhij", 17, 3}, + {"klmnopqrstuvwxyz", 33, 19}, + }}, + }; + + TEST_CASE("log_streambuf2", "[log_streambuf]") { + using xo::xtag; + using xo::print::quot; + using xo::print::unq; + + for (std::size_t i = 0; i < s_testcase_v.size(); ++i) { + INFO(tostr(xtag("i", i))); + + const test_case & tc = s_testcase_v[i]; + + std::size_t z = tc.buf_capacity_; + log_streambuf sbuf(z, true /*debug_flag*/); + std::ostream ss(&sbuf); + + REQUIRE(sbuf.capacity() == z); + REQUIRE(sbuf.pos() == 0); + REQUIRE(sbuf.lpos() == 0); + REQUIRE(sbuf.lo() + sbuf.capacity() == sbuf.hi()); + + std::size_t j = 0; + for (const recd & r : tc.steps_) { + INFO(tostr(xtag("j", j), xtag("text", unq(r.text_)))); + + ss << r.text_; + + REQUIRE(ss.good()); + REQUIRE(sbuf.capacity() >= z); + REQUIRE(sbuf.lo() + sbuf.capacity() == sbuf.hi()); + REQUIRE(sbuf.pos() == r.expect_pos_); + REQUIRE(sbuf.lpos() == r.expect_lpos_); + + ++j; + } + } + } +} + +/* end log_streambuf.test.cpp */ diff --git a/utest/pretty_vector.test.cpp b/utest/pretty_vector.test.cpp new file mode 100644 index 00000000..dc608b2e --- /dev/null +++ b/utest/pretty_vector.test.cpp @@ -0,0 +1,97 @@ +/* @file pretty_vector.test.cpp */ + +#include "xo/indentlog/print/pretty.hpp" +#include "xo/indentlog/print/pretty_vector.hpp" +#include +#include + +namespace ut { + using xo::print::ppconfig; + using xo::print::ppstate_standalone; + + TEST_CASE("print-upto", "[pretty]") { + std::stringstream ss; + ppconfig cfg; + ppstate_standalone pps(&ss, 0, &cfg); + + REQUIRE(pps.pos() == 0); + REQUIRE(pps.lpos() == 0); + REQUIRE(pps.avail_margin() == cfg.right_margin_); + REQUIRE(pps.has_margin()); + REQUIRE(pps.has_budget(cfg.right_margin_)); + REQUIRE(pps.scan_no_newline(0)); + } + + TEST_CASE("pretty", "[pretty]") { + ppconfig ppc; + ppc.right_margin_ = 40; + ppc.indent_width_ = 2; + + std::stringstream ss; + ppstate_standalone pps(&ss, 0, &ppc); + + pps.pretty("hello"); + + REQUIRE(ss.str() == "hello"); + } + + TEST_CASE("prettyvec", "[pretty]") { + ppconfig ppc; + ppc.right_margin_ = 20; + ppc.indent_width_ = 2; + + std::stringstream ss; + ppstate_standalone pps(&ss, 0, &ppc); + + std::vector test = {1, 2, 3, 4, 5, 6}; + + pps.pretty(test); + + REQUIRE(ss.str() == "[1, 2, 3, 4, 5, 6]"); + } + + TEST_CASE("prettyvec2", "[pretty]") { + ppconfig ppc; + ppc.right_margin_ = 10; + ppc.indent_width_ = 2; + + std::stringstream ss; + ppstate_standalone pps(&ss, 0, &ppc); + + std::vector test = {1, 2, 3, 4, 5, 6}; + + pps.pretty(test); + + REQUIRE(ss.str() == "[\n 1,\n 2,\n 3,\n 4,\n 5,\n 6\n]"); + } + + TEST_CASE("prettyvec3", "[pretty]") { + ppconfig ppc; + ppc.right_margin_ = 20; + ppc.indent_width_ = 2; + + std::stringstream ss; + ppstate_standalone pps(&ss, 0, &ppc); + + std::vector> test = {{1, 2, 3, 4}, {4, 5, 6, 7}}; + + pps.pretty(test); + + REQUIRE(ss.str() == "[\n [1, 2, 3, 4],\n [4, 5, 6, 7]\n]"); + } + + TEST_CASE("prettyvec4", "[pretty]") { + ppconfig ppc; + ppc.right_margin_ = 10; + ppc.indent_width_ = 2; + + std::stringstream ss; + ppstate_standalone pps(&ss, 0, &ppc); + + std::vector> test = {{1, 2, 3, 4}, {4, 5, 6, 7}}; + + pps.pretty(test); + + REQUIRE(ss.str() == "[\n [\n 1,\n 2,\n 3,\n 4\n ],\n [\n 4,\n 5,\n 6,\n 7\n ]\n]"); + } +} diff --git a/utest/toppstr.test.cpp b/utest/toppstr.test.cpp new file mode 100644 index 00000000..b06f8069 --- /dev/null +++ b/utest/toppstr.test.cpp @@ -0,0 +1,55 @@ +/* @file toppstr.cpp */ + +#include "xo/indentlog/print/ppstr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +namespace ut { + using xo::toppstr; + using xo::toppstr2; + using xo::print::ppconfig; + using xo::print::ppstate; + + TEST_CASE("toppstr_1", "[toppstr]") { + std::string s = toppstr(); + REQUIRE(s.empty()); + } + + TEST_CASE("toppstr_2", "[toppstr]") { + std::string s = toppstr("hello"); + REQUIRE(s == "hello"); + } + + TEST_CASE("toppstr_3", "[toppstr]") { + std::string s = toppstr("the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog"); + REQUIRE(s == "the quick brown fox jumps over the lazy dog"); + } + + TEST_CASE("toppstr2_0", "[toppstr2]") { + ppconfig ppc; + ppc.right_margin_ = 40; + ppc.indent_width_ = 0; + + std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog"); + REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog"); + } + + TEST_CASE("toppstr2_1", "[toppstr2]") { + ppconfig ppc; + ppc.right_margin_ = 40; + ppc.indent_width_ = 2; + + std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog"); + REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog"); + } + + TEST_CASE("toppstr2_2", "[toppstr2]") { + ppconfig ppc; + ppc.right_margin_ = 40; + ppc.indent_width_ = 4; + + std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog"); + REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog"); + } +} From 7ddb79fadd60f4c6aeefab86b2655a524706ad12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 13 Jul 2025 21:18:15 -0500 Subject: [PATCH 05/42] xo-indentlog: doc: add indentlog docs [WIP] --- docs/CMakeLists.txt | 9 ++ docs/README | 41 +++++++++ docs/conf.py | 39 +++++++++ docs/glossary.rst | 11 +++ docs/index.rst | 19 ++++ docs/install.rst | 170 ++++++++++++++++++++++++++++++++++++ docs/logging_impl.rst | 65 ++++++++++++++ docs/logging_intro.rst | 57 ++++++++++++ docs/ppconfig-reference.rst | 33 +++++++ docs/ppstr-reference.rst | 36 ++++++++ docs/pretty_impl.rst | 37 ++++++++ docs/scope-reference.rst | 43 +++++++++ 12 files changed, 560 insertions(+) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/README create mode 100644 docs/conf.py create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/logging_impl.rst create mode 100644 docs/logging_intro.rst create mode 100644 docs/ppconfig-reference.rst create mode 100644 docs/ppstr-reference.rst create mode 100644 docs/pretty_impl.rst create mode 100644 docs/scope-reference.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..a04c884c --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo-indentlog/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst install.rst + logging_impl.rst + pretty_impl.rst ppconfig-reference.rst ppstr-reference.rst +) diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..6aff5d41 --- /dev/null +++ b/docs/README @@ -0,0 +1,41 @@ +standalone build + + +-----------------------------------------------+ + | cmake | + | CMakeLists.txt | + | $PREFIX/share/cmake/xo_macros/xo_cxx.cmake | + +-----------------------------------------------+ + | + | +----------------------+ + +------------------------------------------------->| .build/docs/Doxyfile | + | +----------------------+ + | ^ + | (cmake) | + | /------------/ + | | + | +---------------------------------------+ +-----------------+ + +---->| doxygen |--------->| .build/docs/dox | + | | $PREFIX/share/xo-macros/Doxyfile.in | (doxygen)| +- html/ | + | +---------------------------------------+ | +- xml/ | + | +-----------------+ + | | + | |(sphinx) + | | + | v + | +---------------------------------------+ +--------------------+ + \---->| sphinx |------->| .build/docs/sphinx | + | +- conf.py | | +- html/ | + | +- _static/ | +--------------------+ + | +- *.rst | + +---------------------------------------+ + +umbrella build relies on top-level cmake macros + +files + + README this file + CMakeLists.txt build entry point + conf.py sphinx config + _static static files for sphinx + + index.rst toplevel sphinx document; entry point diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a598d229 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo reader documentation' +copyright = '2024-2025, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..82bd0d2d --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,11 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + pp + | Short for `pretty printer`. For example in `ppdetail`, `toppstr`. + + xtag + | Shorthand for `tag with preceding space` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..8a974b60 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,19 @@ +.. xo-indentlog documentation master file. + +xo-indentlog documentation +========================== + +xo-indentlog is a header-only library for human-readable structured logging +and general purpose pretty-printing. + +.. toctree:: + :maxdepth: 2 + :caption: xo-indentlog contents + + install + logging_intro + logging_impl + pretty_impl + glossary + genindex + search diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..7e075e35 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,170 @@ +.. _install: + +.. toctree:: + :maxdepth: 2 + +Source +====== + +Source code lives on github `here`_ + +.. _here: https://github.com/rconybea/indentlog + +To clone from git: + +.. code-block:: bash + + git clone https://github.com/rconybea/indentlog xo-indentlog + +Tested with gcc 13.3 + +Install +======= + +Since ``xo-indentlog`` is header-only, can incorporate into another project by simply copying the +include directories to somewhere convenient + +Copy includes +------------- + +.. code-block:: bash + + # for example + cd myproject + mkdir -p ext/xo-indentlog + rsync -a -v path/to/xo-indentlog/include/ ext/xo-indentlog/ + +Include as git submodule +------------------------ + +.. code-block::bash + + cd myproject + git submodule add -b main https://github.com/rconybea/indentlog ext/xo-indentlog + git submodule update --init ext/xo-indentlog + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-indentlog/include`` to your compiler's include path, +and from c++ do something like + +.. code-block:: c++ + + #include + #include + +in c++ source files that rely on xo-indentlog and pretty printing + +Install along with XO +--------------------- + +Install along with the rest of *XO* from `xo-umbrella2 source`_ + +.. _xo-umbrella2 source: https://github.com/rconybea/xo-umbrella2 + +(instructions in xo-umbrella2/README.md) + +Building from source +-------------------- + +Although the xo-indentlog library is header-only, unit tests and examples have additional dependencies. +Example instructions for build strating from stock ubuntu (i.e. github CI) are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/indentlog/blob/main/.github/workflows/ubuntu-main.yml + +Unit-test and example dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros (shared with other XO projects) + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake + +To build documentation, will also need: + +* `doxygen` +* `graphviz` +* `sphinx` +* `breathe` +* `sphinx_rtd_theme` +* `sphinxcontrib.ditaa` + +Preamble: + +.. code-block:: bash + + mkdir -p ~/proj/xo + cd ~/proj/xo + + git clone https://github/com/rconybea/xo-cmake + + PREFIX=/usr/local + + # want PREFIX/bin in PATH to use xo-cmake helpers + PATH=$PREFIX/bin:$PATH + +Install `xo-cmake`: + +.. code-block:: bash + + cmake -B xo-cmake/.build -S xo-cmake + cmake --install xo-cmake/.build + xo-build --clone --configure --build --install xo-indentlog + + # or with optional components + xo-build --clone --configure --build --install --with-examples --with-utests xo-indentlog + +Note that you can instead have ``xo-build`` tell you what it /would/ do: + +.. code-block:: bash + + xo-build --configure -dry-run xo-indentlog + cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_MODULE_PATH=/usr/local/share/cmake -S xo-indentlog -B xo-indentlog/.build -DENABLE_TESTING=1 -DXO_ENABLE_EXAMPLES=1 -DCMAKE_BUILD_TYPE=Debug + + +Directories under ``PREFIX`` will then contain: + +.. code-block:: + + PREFIX + +- bin + | +- xo-build + | +- xo-cmake-config + | \- xo-cmake-lcov-harness + +- include + | \- xo + | \- indentlog/ + +- lib + | \- cmake + | +- indentlog/ + +- share + +- cmake + | \- xo_macros + | +- code-coverage.cmake + | +- xo-project-macros.cmake + | \- xo_cxx.cmake + +- etc + | \- xo + | \- subsystem-list + \- xo-macros + +- Doxyfile.in + +- gen-ccov.in + \- xo-bootstrap-macros.cmake + +CMake Support +------------- + +To use built-in cmake support to use installed ``xo-indentlog`` from another project: + +Make sure ``PREFIX/lib/cmake`` is searched by cmake (for example include it in ``CMAKE_PREFIX_PATH``) + +Add to your ``CMakeLists.txt``: + +.. code-block:: cmake + + FindPackage(indentlog CONFIG REQUIRED) + target_link_libraries(mytarget INTERFACE indentlog) + +Supported compilers +------------------- + +* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024) diff --git a/docs/logging_impl.rst b/docs/logging_impl.rst new file mode 100644 index 00000000..f5d0f91c --- /dev/null +++ b/docs/logging_impl.rst @@ -0,0 +1,65 @@ +.. _logging_impl: + +Logging Components +================== + +Install instructions :doc:`here` + +Abstraction tower for *xo-indentlog* logging components: + +.. ditaa:: + :--scale: 0.85 + + +------------------------------------------+ + | basic_scope | + +------------------------------------------+ + | state_impl | + +-----------------------+------------------+ + | | log_config | + | log_streambuf +------------------+ + | | log_level | + +-----------------------+------------------+ + +**basic_scope** + Primary entry point for scope-indented logging. + Lightweight with RAII semantics. + +**state_impl** + Bookkeeping for internal state, managed by ``basic_scope``. + +**log_streambuf** + Custom streambuf implementation, buffering in memory. + +**log_config** + Logging configuration, to control formatting behavior. + +**log_level** + Categorize log messages by importance for filtering. + +.. toctree:: + :maxdepth: 2 + + xo::scope + + +Log_streambuf +------------- + +Indentlog relies on a custom ``std::streambuf`` implementation, ``xo::log_streambuf``. +log_streambuf captures content in an internal vector, expanding it on demand. +To support pretty-printing, log_streambuf tracks current position relative to left margin + +.. uml:: + :caption: log_streambuf + :scale: 99% + :align: center + + class streambuf { + } + + class log_streambuf { + - solpos_: size_t + - buf_v_: vector + } + + streambuf <|-- log_streambuf diff --git a/docs/logging_intro.rst b/docs/logging_intro.rst new file mode 100644 index 00000000..acd76449 --- /dev/null +++ b/docs/logging_intro.rst @@ -0,0 +1,57 @@ +.. _logging-intro: + +Introduction +============ + +Indentlog provides is a logging library that autoindents to reveal the calling program's call graph. + +Features +-------- + +* **Indenting** Nesting level and is consistent with program structure. + +* **Pretty-Printer** Includes configurable, general-purpose pretty-printer. + +* **Efficient** Uses custom streambuf for efficiency. + Avoids executing code to assemble input values that would be discarded below currently configured + logging level + +* **Configurable** configure from global configuration object `logconfig`. + +* **Thread-aware** logging state is accessed from thread-local variables. + Logging for different threads is assembled in thread-local state for improved concurrency. + +* **Colorized** generate colorized output, depending on configuration. + +Examples +-------- + +.. code-block:: cpp + + #include "xo/indentlog/scope.hpp" + + using namespace xo; + + void inner(int x) { + scope log(XO_ENTER0(always), ":x ", x); + } + + void outer(int y) { + scope log(XO_ENTER0(always), ":y ", y); + + inner(2*y); + } + + int + main(int argc, char ** argv) { + outer(123); + } + +output: + +.. code-block:: text + + 15:28:35.702611 +(0) outer :y 123 [ex1.cpp:12] + 15:28:35.702723 +(1) inner :x 246 [ex1.cpp:8] + 15:28:35.702737 -(1) inner + 15:28:35.702760 -(0) outer diff --git a/docs/ppconfig-reference.rst b/docs/ppconfig-reference.rst new file mode 100644 index 00000000..f511abde --- /dev/null +++ b/docs/ppconfig-reference.rst @@ -0,0 +1,33 @@ +.. _ppconfig-reference: + +Ppconfig Reference +================== + +.. ditaa:: + :--scale: 0.85 + + +--------------------+ + | toppstr | + +--------------------+ + | ppdetail | + +--------------------+ + | ppstate | + +--------------------+ + |cBLU ppconfig | + +--------------------+ + +.. code-block:: cpp + + #include + +Class +----- + +Configuration for pretty printing + +.. doxygenclass:: xo::print::ppconfig + +Member Variables +---------------- + +.. doxygengroup:: ppconfig-instance-vars diff --git a/docs/ppstr-reference.rst b/docs/ppstr-reference.rst new file mode 100644 index 00000000..a8f24034 --- /dev/null +++ b/docs/ppstr-reference.rst @@ -0,0 +1,36 @@ +.. _ppstr-reference: + +.. toctree:: + :maxdepth: 2 + +Ppstr Reference +=============== + +.. ditaa:: + :--scale: 0.85 + + +--------------------+ + |cBLU ppstr | + +--------------------+ + | ppdetail | + +--------------------+ + | ppstate | + +--------------------+ + | ppconfig | + +--------------------+ + +.. code-block:: cpp + + #include + +Types +----- + +.. doxygenclass:: xo::ppconcat + +Functions +--------- + +.. doxygenfunction:: xo::toppstr + +.. doxygenfunction:: xo::toppstr2 diff --git a/docs/pretty_impl.rst b/docs/pretty_impl.rst new file mode 100644 index 00000000..45e08575 --- /dev/null +++ b/docs/pretty_impl.rst @@ -0,0 +1,37 @@ +.. _pretty_impl: + +Pretty Printer +============== + +Abstraction tower for *xo-indentlog* pretty-printer: + +.. ditaa:: + :--scale: 0.85 + + +--------------------+ + | toppstr | + +--------------------+ + | ppdetail | + +--------------------+ + | ppstate | + +--------------------+ + | ppconfig | + +--------------------+ + +**toppstr** + Pretty print arguments to string. + +**ppdetail** + Template for Type-specific pretty-printing. + +**ppstate** + Temporary state object. Created once for each top-level pretty-print call + +**ppconfig** + Pretty-printer configuration. + +.. toctree:: + :maxdepth: 1 + + ppstr functions + ppconfig class diff --git a/docs/scope-reference.rst b/docs/scope-reference.rst new file mode 100644 index 00000000..abf199ae --- /dev/null +++ b/docs/scope-reference.rst @@ -0,0 +1,43 @@ +.. _scope-reference: + +.. toctree:: + :maxdepth: 2 + +Scope Reference +=============== + +.. ditaa:: + :--scale: 0.85 + + +------------------------------------------+ + |cBLU basic_scope | + +------------------------------------------+ + | state_impl | + +-----------------------+------------------+ + | | log_config | + | log_streambuf +------------------+ + | | log_level | + +-----------------------+------------------+ + +.. code-block:: cpp + + #include + +Convenience Macros +------------------ + +.. doxygendefine:: XO_SCOPE + +Classes +------- + +.. doxygenclass:: xo::scope_setup + +Application code isn't expected to interact with this class directly + +.. doxygengroup:: scope_setup-instance-vars + +Basic Scope +~~~~~~~~~~~ + +.. doxygenclass:: xo::basic_scope From c2dcc84c2e916fa4ecea4863e28c619e09501e5e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 19 Jul 2025 11:47:03 -0500 Subject: [PATCH 06/42] pretty printing -- copmlete for xo::ast::GeneralizedExpression --- docs/glossary.rst | 19 +- include/xo/indentlog/log_streambuf.hpp | 125 +++++++-- include/xo/indentlog/print/color.hpp | 43 ++- include/xo/indentlog/print/concat.hpp | 7 +- include/xo/indentlog/print/ppconfig.hpp | 5 +- .../xo/indentlog/print/ppdetail_atomic.hpp | 99 +++---- include/xo/indentlog/print/ppstr.hpp | 55 ++-- include/xo/indentlog/print/pretty.hpp | 251 +++++++++++++++--- include/xo/indentlog/print/pretty_concat.hpp | 14 - include/xo/indentlog/print/pretty_tag.hpp | 56 ---- include/xo/indentlog/print/pretty_vector.hpp | 79 +++--- include/xo/indentlog/print/quoted.hpp | 16 +- include/xo/indentlog/print/tag.hpp | 95 ++++++- include/xo/indentlog/scope.hpp | 2 - 14 files changed, 573 insertions(+), 293 deletions(-) delete mode 100644 include/xo/indentlog/print/pretty_concat.hpp delete mode 100644 include/xo/indentlog/print/pretty_tag.hpp diff --git a/docs/glossary.rst b/docs/glossary.rst index 82bd0d2d..0631a3fd 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 855fc05b..b6e9305d 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 bcd3e634..ef91f49f 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 3c166c41..e9d4905d 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 1cc60597..67d1831a 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 05614eed..89824028 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 358c14b6..f5f2ad5b 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 369713b0..c6ed3a40 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 8b484936..00000000 --- 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 7402136b..00000000 --- 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 efccb800..6a05fe14 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 6de2bb95..c34d099f 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 ad55f3c2..008dcbdd 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 10cabf8e..3217e0e8 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 From c1446de13defb0517dfb87014e424215bf21a9cd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 24 Jul 2025 07:50:34 -0500 Subject: [PATCH 07/42] xo-reader: work on apply expression parsing --- include/xo/indentlog/print/tag.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index 008dcbdd..678eb0f7 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -158,6 +158,17 @@ namespace xo { return ref_tag_impl, std::decay_t>(n, v); } + // ----- xrefrtag ----- + + /** 'reference raw tag'. Like @ref refrtag, but precede with a single space + **/ + template + auto + xrefrtag(Name && n, Value && v) + { + return ref_tag_impl, std::decay_t>(n, v); + } + // ----- operator<< on tag_impl ----- template From 4b6f52291cf7773c71d2ec5ddbc5541580985f05 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 25 Jul 2025 10:42:40 -0400 Subject: [PATCH 08/42] build: osx carveouts --- include/xo/indentlog/print/ppdetail_atomic.hpp | 1 + include/xo/indentlog/print/pretty.hpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 89824028..d8036fe6 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -23,6 +23,7 @@ namespace xo { : pps_{pps}, ci0_{ci0}, ci1_{ci0 + indent_width}, upto_{upto} {} ppstate * pps() const { return pps_; } + std::uint32_t ci0_unused() const { return ci0_; } std::uint32_t ci1() const { return ci1_; } bool upto() const { return upto_; } diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index c6ed3a40..9f17d64d 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -39,6 +39,8 @@ namespace xo { uint32_t indent_width() const { return config_->indent_width_; } ppindentinfo indent_info(bool upto) { return ppindentinfo(this, lpos(), indent_width(), upto); } + std::uint32_t current_indent_unused() const { return current_indent_; } + std::uint32_t pos() const { return scratch_sbuf_->pos(); } /** current position relative to start of line (last \n or \r), * excluding color escape sequences @@ -170,7 +172,7 @@ namespace xo { /** readonly pretty-printer config **/ const ppconfig * config_ = nullptr; - }; + }; /*ppstate*/ /** @class ppstate_standalone * @brief like ppstate, but also holds streambuf From 56a8cfa2a302f239bfe6c74dc30cf061b02c1a76 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 26 Jul 2025 17:28:41 -0400 Subject: [PATCH 09/42] xo-expression xo-reader: type unifier + misc improvements --- include/xo/indentlog/print/ppdetail_atomic.hpp | 8 ++++---- include/xo/indentlog/print/pretty.hpp | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index d8036fe6..3a14399b 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -13,10 +13,10 @@ namespace xo { 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. -#define ppdetail_atomic ppdetail + // 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. +//#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index 9f17d64d..aa50ca3c 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -440,7 +440,8 @@ namespace xo { ppstate & ppstate::newline_pretty_tag(std::uint32_t ci, Name && name, Value && value) { - newline_indent(ci); + this->newline_indent(ci); + this->current_indent_ = ci; this->pretty(rtag(name, value)); return *this; From caf5a2b4757ad9910fb450989c3824c980d6184c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 27 Jul 2025 13:35:20 -0400 Subject: [PATCH 10/42] xo-reader: integer arithmetic + parser + pretty-printing adds --- include/xo/indentlog/print/ppdetail_atomic.hpp | 1 + include/xo/indentlog/print/pretty.hpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 3a14399b..795eae1d 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -112,6 +112,7 @@ namespace xo { PPDETAIL_ATOMIC(bool); PPDETAIL_ATOMIC(char); + PPDETAIL_ATOMIC(unsigned long); PPDETAIL_ATOMIC(std::int64_t); PPDETAIL_ATOMIC(std::uint64_t); PPDETAIL_ATOMIC(std::int32_t); diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index aa50ca3c..8068a297 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -107,7 +107,7 @@ namespace xo { /** pretty-print empty struct **/ template - std::uint32_t pretty_struct(const ppindentinfo & ppii, StructName && structname, Members&&...); + bool 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. @@ -224,7 +224,7 @@ namespace xo { }; template - std::uint32_t + bool ppstate::pretty_struct(const ppindentinfo & ppii, StructName && structname, Members&&... members) { if (ppii.upto()) { From e2254881df03cc93bde8b1fe5a40735c3f5eaf59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 27 Jul 2025 14:32:48 -0400 Subject: [PATCH 11/42] xo-indentlog: bugfix: ppdetail unsigned long only on appl --- include/xo/indentlog/print/ppdetail_atomic.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 795eae1d..aa1f469a 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -112,7 +112,10 @@ namespace xo { PPDETAIL_ATOMIC(bool); PPDETAIL_ATOMIC(char); +#ifdef __APPLE__ + // unsigned long != std::uint64_t. PPDETAIL_ATOMIC(unsigned long); +#endif PPDETAIL_ATOMIC(std::int64_t); PPDETAIL_ATOMIC(std::uint64_t); PPDETAIL_ATOMIC(std::int32_t); From cfd80f73d1129e5e442e373f8bf0bdc8d25b232e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 28 Jul 2025 10:13:25 -0400 Subject: [PATCH 12/42] refactor xo::ast -> xo::scm + restore nodef ppdetail_atomic build --- include/xo/indentlog/print/ppdetail_atomic.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index aa1f469a..dfeaf415 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // 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. -//#define ppdetail_atomic ppdetail +#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) @@ -127,7 +127,7 @@ namespace xo { using voidptr = void*; PPDETAIL_ATOMIC(voidptr); -#endif +#endif // #ifndef ppdetail_atomic } /*namespace print*/ } /*namespace xo*/ From e5d55ab5af53ae3aaae7b902ddfc64062372e37d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 28 Jul 2025 15:16:11 -0400 Subject: [PATCH 13/42] minor logging adds --- include/xo/indentlog/print/ppdetail_atomic.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index dfeaf415..3f460a76 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // 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. -#define ppdetail_atomic ppdetail +//#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) From 4f6bb25406fee6f9d8f7d172a4e5ce17d309410d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 3 Aug 2025 13:56:49 -0500 Subject: [PATCH 14/42] redef ppdetail_atomic --- include/xo/indentlog/print/ppdetail_atomic.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 3f460a76..dfeaf415 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // 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. -//#define ppdetail_atomic ppdetail +#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) From c13b2a502c36c3170f3c75f1325bfd94d50dc5d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 09:31:10 -0500 Subject: [PATCH 15/42] xo-indentlog: fix pretty-print utests --- utest/log_streambuf.test.cpp | 2 +- utest/pretty_vector.test.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index e145bd96..12b3b388 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -78,7 +78,7 @@ namespace ut { const test_case & tc = s_testcase_v[i]; std::size_t z = tc.buf_capacity_; - log_streambuf sbuf(z, true /*debug_flag*/); + log_streambuf sbuf(z, false /*debug_flag*/); std::ostream ss(&sbuf); REQUIRE(sbuf.capacity() == z); diff --git a/utest/pretty_vector.test.cpp b/utest/pretty_vector.test.cpp index dc608b2e..47cad29b 100644 --- a/utest/pretty_vector.test.cpp +++ b/utest/pretty_vector.test.cpp @@ -62,7 +62,7 @@ namespace ut { pps.pretty(test); - REQUIRE(ss.str() == "[\n 1,\n 2,\n 3,\n 4,\n 5,\n 6\n]"); + REQUIRE(ss.str() == "[ 1,\n 2,\n 3,\n 4,\n 5,\n 6 ]"); } TEST_CASE("prettyvec3", "[pretty]") { @@ -77,7 +77,7 @@ namespace ut { pps.pretty(test); - REQUIRE(ss.str() == "[\n [1, 2, 3, 4],\n [4, 5, 6, 7]\n]"); + REQUIRE(ss.str() == "[ [1, 2, 3, 4],\n [4, 5, 6, 7] ]"); } TEST_CASE("prettyvec4", "[pretty]") { @@ -92,6 +92,6 @@ namespace ut { pps.pretty(test); - REQUIRE(ss.str() == "[\n [\n 1,\n 2,\n 3,\n 4\n ],\n [\n 4,\n 5,\n 6,\n 7\n ]\n]"); + REQUIRE(ss.str() == "[ [ 1,\n 2,\n 3,\n 4 ],\n [ 4,\n 5,\n 6,\n 7 ] ]"); } } From 187eae521fe736bd573994d9036fbb552151b9cf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 13:53:31 -0500 Subject: [PATCH 16/42] xo-object: improve GC unittest + prep to integrate w/ xo::reflect --- include/xo/indentlog/print/ppdetail_atomic.hpp | 2 +- include/xo/indentlog/print/pretty_vector.hpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index dfeaf415..3f460a76 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // 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. -#define ppdetail_atomic ppdetail +//#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) diff --git a/include/xo/indentlog/print/pretty_vector.hpp b/include/xo/indentlog/print/pretty_vector.hpp index 6a05fe14..b22fc149 100644 --- a/include/xo/indentlog/print/pretty_vector.hpp +++ b/include/xo/indentlog/print/pretty_vector.hpp @@ -6,8 +6,11 @@ #pragma once #include "pretty.hpp" +#include "array.hpp" /*printing*/ #include "pad.hpp" #include +#include +#include namespace xo { namespace print { @@ -54,5 +57,12 @@ namespace xo { return ppdetail_vector>::print_pretty(ppii, x); } }; + + template + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, const std::array & x) { + return ppdetail_vector>::print_pretty(ppii, x); + } + }; } } From 8f690edc38290f596439a539a24f606cdb8fab0b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 22:34:20 -0500 Subject: [PATCH 17/42] xo-allod: + per-type stats + pretty printing --- include/xo/indentlog/print/pretty_vector.hpp | 39 ++++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/include/xo/indentlog/print/pretty_vector.hpp b/include/xo/indentlog/print/pretty_vector.hpp index b22fc149..8a5bc0d9 100644 --- a/include/xo/indentlog/print/pretty_vector.hpp +++ b/include/xo/indentlog/print/pretty_vector.hpp @@ -34,15 +34,38 @@ namespace xo { } 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]); + bool skip_next_indent = false; - if (i+1 < z) - ppii.pps()->write(','); + for (size_t i = 0, z = x.size(); i < z; ++i) { + if (!skip_next_indent) { + if (i == 0) + ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1); + else + ppii.pps()->newline_indent(ppii.ci1()); + } + + std::uint32_t pos0 = ppii.pps()->pos(); + ppii.pps()->pretty(x[i]); + std::uint32_t pos1 = ppii.pps()->pos(); + + bool skip_prev_indent = skip_next_indent; + + /* special case, did not print anything */ + skip_next_indent = (pos0 == pos1); + + if (skip_next_indent) { + if (skip_prev_indent) { + /* compress consecutive empty items */ + ; + } else { + /* catch edge from non-empty -> empty */ + ppii.pps()->write(",.. ,"); + ppii.pps()->newline_indent(ppii.ci1()); + } + } else { + if (i+1 < z) + ppii.pps()->write(','); + } } ppii.pps()->write(" ]"); From f411b2683ce9fc4b842d31087b10a02572d36451 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 7 Aug 2025 18:32:14 -0500 Subject: [PATCH 18/42] xo-alloc / xo-object: utest coverage + assorted bugfixes --- include/xo/indentlog/print/color.hpp | 30 ++++++++++++++++++----- include/xo/indentlog/print/pretty.hpp | 22 ++++++++++++----- include/xo/indentlog/print/tag.hpp | 4 +-- include/xo/indentlog/print/tag_config.hpp | 17 ++++++++++--- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/include/xo/indentlog/print/color.hpp b/include/xo/indentlog/print/color.hpp index ef91f49f..268b6d9e 100644 --- a/include/xo/indentlog/print/color.hpp +++ b/include/xo/indentlog/print/color.hpp @@ -144,7 +144,8 @@ namespace xo { class color_impl { public: color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents) - : flags_{flags}, spec_{spec}, contents_{std::forward(contents)} {} + : flags_{flags}, + spec_{spec}, contents_{std::forward(contents)} {} color_spec_type const & spec() const { return spec_; } std::uint32_t color() const { return spec_.code(); } @@ -162,32 +163,49 @@ namespace xo { } /*print*/ private: - /* controls independently what to print + /** controls independently what to print * \033[38;5;117m hello, world! \033[0m * <------------> <-----------> <-----> * color_on contents color_off - */ + **/ coloring_control_flags flags_ = coloring_control_flags::none; + /** color to use, when @ref color_enabled_ is on **/ color_spec_type spec_; + /** contents to print surrounded by color escapes **/ Contents contents_; }; /*color_impl*/ + template + color_impl with_color_if(bool color_enabled, color_spec_type const & spec, Contents && contents) { + return color_impl((color_enabled + ? coloring_control_flags::all + : coloring_control_flags::contents), + spec, + std::forward(contents)); + } + template color_impl with_color(color_spec_type const & spec, Contents && contents) { - return color_impl(coloring_control_flags::all, spec, std::forward(contents)); + return color_impl(coloring_control_flags::all, + spec, + std::forward(contents)); } inline color_impl color_on(color_spec_type const & spec) { - return color_impl(coloring_control_flags::color_on, spec, 0); + return color_impl(coloring_control_flags::color_on, + spec, + 0); } 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); + return color_impl(coloring_control_flags::color_off, + spec, + 0); } template diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index 8068a297..3ae010eb 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -20,12 +20,19 @@ namespace xo { /** @class ppstate * @brief hold pretty-printer state * + * Need a log_streambuf instance to keep track of indent. + * Application code will likely prefer @ref pretty_printer + * * Use: + * @code * ppconfig ppc; - * ppstate pps(&cout, 0, &ppc); + * log_streambuf sbuf(buf_z); + * stringstream ss(&sbuf); + * ppstate pps(0, &ppc, &ss, &sbuf); * * pps.pretty("first"); * pps.pretty("second"); + * @endcode **/ struct ppstate { using streambuf_type = log_streambuf>; @@ -176,6 +183,8 @@ namespace xo { /** @class ppstate_standalone * @brief like ppstate, but also holds streambuf + * + * editor bait: pretty_printer prettyprinter */ struct ppstate_standalone : public ppstate { explicit ppstate_standalone(std::ostream * os, std::uint32_t ci, const ppconfig * config) @@ -377,8 +386,9 @@ namespace xo { 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())))) + if (!ppii.pps()->print_upto(with_color_if(tag_config::tag_color_enabled, + color_spec_type::yellow(), // tag_config::tag_color, + concat((char const *)":", tag.name())))) return false; ppii.pps()->write(" "); @@ -395,8 +405,9 @@ namespace xo { 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()->write(with_color_if(tag_config::tag_color_enabled, + color_spec_type::yellow(), //tag_config::tag_color, + concat((char const *)":", tag.name()))); ppii.pps()->newline_indent(ppii.ci1()); ppii.pps()->pretty(tag.value()); @@ -446,6 +457,5 @@ namespace xo { return *this; } - } /*namespace print*/ } /*namespace xo*/ diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index 678eb0f7..026e8205 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -181,7 +181,7 @@ namespace xo { if (PrefixSpace) s << " "; - s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) + s << with_color_if(tag_config::tag_color_enabled, tag_config::tag_color, concat((char const *)":", tag.name())) << " "; if (TagStyle == tagstyle::autoescape) @@ -204,7 +204,7 @@ namespace xo { if (PrefixSpace) s << " "; - s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) + s << with_color_if(tag_config::tag_color_enabled, tag_config::tag_color, concat((char const *)":", tag.name())) << " "; if (TagStyle == tagstyle::autoescape) diff --git a/include/xo/indentlog/print/tag_config.hpp b/include/xo/indentlog/print/tag_config.hpp index f86b2c8f..e9c76cd5 100644 --- a/include/xo/indentlog/print/tag_config.hpp +++ b/include/xo/indentlog/print/tag_config.hpp @@ -6,18 +6,29 @@ #include namespace xo { - /* Tag here b/c we want header-only library */ + /** @class tag_config_impl + * @brief configuration for tag-printing + * + * note: Tag here b/c we want header-only library + **/ template struct tag_config_impl { - /* color to use for tags + /** true to enable colored tag printing **/ + static bool tag_color_enabled; + /** color to use for tags * os << tag("foo", foovalue) * to produces output like * :foo foovalue * with :foo using .tag_color - */ + **/ static color_spec_type tag_color; }; /*tag_config_impl*/ + template + bool + tag_config_impl::tag_color_enabled = true; + + /** default value is a light gray **/ template color_spec_type tag_config_impl::tag_color = color_spec_type::xterm(245); From efa36e428803d4e4fa3e6a54c2f69860a9176472 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Sep 2025 23:47:32 -0400 Subject: [PATCH 19/42] xo-indentlog: ppdetail_atomic -> ppdetail --- include/xo/indentlog/print/ppdetail_atomic.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 3f460a76..dfeaf415 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // 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. -//#define ppdetail_atomic ppdetail +#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) From 4494071540d4ef705952b399c98374f3c87cacde Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 22 Sep 2025 12:25:36 -0400 Subject: [PATCH 20/42] xo-indentlog: bugfix: commit should have ppdetail_stomic->ppdetail --- include/xo/indentlog/print/ppdetail_atomic.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 3f460a76..3f2a3c3f 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -13,10 +13,14 @@ namespace xo { struct ppstate; // see pretty.hpp struct ppindentinfo; - // Defining this means ppdetail_atomic is not used. + // Defining this means ppdetail_atomic -> ppdetail. + // For debugging suppress the #define; causes specialization of ppdetail_atomic to be required for every T + // that participates in pretty printing + // // 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. -//#define ppdetail_atomic ppdetail + // +#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) From 0b5c9c362006e0766c12ccd8318cfe3f62af2efe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 22 Sep 2025 12:52:30 -0400 Subject: [PATCH 21/42] xo-indentlog: build: + favicon for docs build --- docs/_static/README | 1 + docs/_static/img/favicon.ico | Bin 0 -> 309803 bytes 2 files changed, 1 insertion(+) create mode 100644 docs/_static/README create mode 100644 docs/_static/img/favicon.ico diff --git a/docs/_static/README b/docs/_static/README new file mode 100644 index 00000000..8230095c --- /dev/null +++ b/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here \ No newline at end of file diff --git a/docs/_static/img/favicon.ico b/docs/_static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..15da2145f93eb26e6995c7868ab7883fb2361288 GIT binary patch literal 309803 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 Date: Mon, 22 Sep 2025 12:53:12 -0400 Subject: [PATCH 22/42] xo-indentlog: build: cosmetic --- example/ex1/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 69f49e17..3b482907 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,4 +1,7 @@ +set(SELF_EXE indentlog_ex1) +set(SELF_SRCS ex1.cpp) + if (XO_ENABLE_EXAMPLES) - add_executable(indentlog_ex1 ex1.cpp) + add_executable(${SELF_EXE} ${SELF_SRCS}) xo_include_options2(indentlog_ex1) endif() From fcde0a4eb3390b7a5a5c49e0060de17eb68ff733 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Dec 2025 10:56:10 -0500 Subject: [PATCH 23/42] xo-ordinaltree: 3-way comparison scaffolding. wip --- include/xo/indentlog/timeutil/timeutil.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/indentlog/timeutil/timeutil.hpp b/include/xo/indentlog/timeutil/timeutil.hpp index 1a2a0240..53a70f6c 100644 --- a/include/xo/indentlog/timeutil/timeutil.hpp +++ b/include/xo/indentlog/timeutil/timeutil.hpp @@ -8,6 +8,7 @@ # include #endif #include +#include #include #include From d459fa5a3937a8a50d7e2a17169993443b01af10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 4 Jan 2026 23:03:18 -0500 Subject: [PATCH 24/42] + xo-printable2 + build fixes for cmake config --- .../xo/indentlog/print/ppdetail_atomic.hpp | 30 ++----------- include/xo/indentlog/print/ppindentinfo.hpp | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 include/xo/indentlog/print/ppindentinfo.hpp diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index 3f2a3c3f..cc50770a 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -1,17 +1,17 @@ -/* ppdetail_atomic.hpp +/** @file ppdetail_atomic.hpp * - * author: Roland Conybeare, Jul 2025 - */ + * @author Roland Conybeare, Jul 2025 + **/ #pragma once +#include "ppindentinfo.hpp" #include #include namespace xo { namespace print { struct ppstate; // see pretty.hpp - struct ppindentinfo; // Defining this means ppdetail_atomic -> ppdetail. // For debugging suppress the #define; causes specialization of ppdetail_atomic to be required for every T @@ -22,28 +22,6 @@ namespace xo { // #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 ci0_unused() const { return ci0_; } - 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 * diff --git a/include/xo/indentlog/print/ppindentinfo.hpp b/include/xo/indentlog/print/ppindentinfo.hpp new file mode 100644 index 00000000..6137e6f9 --- /dev/null +++ b/include/xo/indentlog/print/ppindentinfo.hpp @@ -0,0 +1,43 @@ +/** @file ppindentinfo.hpp + * + * @author Roland Conybeare, Jul 2025 + **/ + +#pragma once + +#include + +namespace xo { + namespace print { + struct ppstate; + + /** @class ppindentinfo + * @brief pretty-printing state passed down the stack while traversing object graph + **/ + 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 ci0_unused() const { return ci0_; } + std::uint32_t ci1() const { return ci1_; } + bool upto() const { return upto_; } + + private: + /** pretty-printing state. flyweight. See [xo/indentlog/print/pretty.hpp] **/ + 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_; + }; + + } +} + +/* end ppindentinfo.hpp */ From ffb6b289ddbe4b61f1c9f0b54cc8120f3c11b116 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Jan 2026 11:53:44 -0500 Subject: [PATCH 25/42] xo-object2: APrintable+DFloat --- include/xo/indentlog/print/ppdetail_atomic.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/indentlog/print/ppdetail_atomic.hpp b/include/xo/indentlog/print/ppdetail_atomic.hpp index cc50770a..8c7261d9 100644 --- a/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -94,10 +94,10 @@ namespace xo { PPDETAIL_ATOMIC(bool); PPDETAIL_ATOMIC(char); -#ifdef __APPLE__ +# ifdef __APPLE__ // unsigned long != std::uint64_t. PPDETAIL_ATOMIC(unsigned long); -#endif +# endif PPDETAIL_ATOMIC(std::int64_t); PPDETAIL_ATOMIC(std::uint64_t); PPDETAIL_ATOMIC(std::int32_t); From 315202b1d9f8b7da7ca042104505f44b4bfe51de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Jan 2026 17:56:38 -0500 Subject: [PATCH 26/42] xo-indentlog xo-arena: improve verify_ok logging workflow + scope.retroactively_enable() --- include/xo/indentlog/scope.hpp | 67 +++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/include/xo/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp index 3217e0e8..42a664c9 100644 --- a/include/xo/indentlog/scope.hpp +++ b/include/xo/indentlog/scope.hpp @@ -171,10 +171,27 @@ namespace xo { return true; } /*log*/ + /** re-enable + log (args...). + * No-op if scope already enabled. + * Useful in verify_ok() methods, to conditionally enable + * logging only when a test fails + **/ + template + void retroactively_enable(Tn&&... args) { + if (finalized_) { + this->finalized_ = false; + this->begin_scope(std::forward(args)...); + } + } + /** Log argument in pack @p args **/ template bool operator()(Tn&&... args) { return this->log(std::forward(args)...); } + /** If enabled, writes initial banner **/ + template + void begin_scope(Tn&&... args); + /** Optionally, call once to end scope before dtor. * Logs arguments in pack @p args **/ @@ -231,27 +248,7 @@ namespace xo { line_{setup.line_}, finalized_{!(setup.is_enabled())} { - if(setup.is_enabled()) { - state_impl_type * logstate = basic_scope::require_thread_local_state(); - std::ostream & os = logstate2stream(logstate); - - logstate->preamble(this->style_, this->name1_, this->name2_); - - tosn(os, " ", std::forward(args)...); - - if (log_config::location_enabled) { - /* prints on next call to flush2sbuf */ - logstate->set_location(this->file_, this->line_); - //tosn(os, " [", basename(this->file_), ":", this->line_, "]"); - } - - logstate->flush2sbuf(std::clog.rdbuf()); - - ///* next call to scope::log() can reset to beginning of buffer space */ - //logstate->ss().seekp(0); - - logstate->incr_nesting(); - } + this->begin_scope(std::forward(args)...); } /*ctor*/ template @@ -314,6 +311,34 @@ namespace xo { logstate->flush2sbuf(this->dest_sbuf_); } /*flush2sbuf*/ + template + template + void + basic_scope::begin_scope(Tn&&... args) + { + if(this->enabled()) { + state_impl_type * logstate = basic_scope::require_thread_local_state(); + std::ostream & os = logstate2stream(logstate); + + logstate->preamble(this->style_, this->name1_, this->name2_); + + tosn(os, " ", std::forward(args)...); + + if (log_config::location_enabled) { + /* prints on next call to flush2sbuf */ + logstate->set_location(this->file_, this->line_); + //tosn(os, " [", basename(this->file_), ":", this->line_, "]"); + } + + logstate->flush2sbuf(std::clog.rdbuf()); + + ///* next call to scope::log() can reset to beginning of buffer space */ + //logstate->ss().seekp(0); + + logstate->incr_nesting(); + } + } + template template void From ba9d6d6cba027fe7205b92250de6e5e1bcc4daaf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 16 Jan 2026 20:40:35 -0500 Subject: [PATCH 27/42] xo-indentlog: fix utest -- lpos --- utest/log_streambuf.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index 12b3b388..8098d881 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -22,7 +22,7 @@ namespace ut { REQUIRE(sbuf.capacity() == z); REQUIRE(sbuf.pos() == 1); - REQUIRE(sbuf.lpos() == 1); + REQUIRE(sbuf.lpos() == 0); // at least on OSX } // write test cases with some random strings. From 1e7feed89767b81c5c9359e593de2a651c33a370 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 17 Jan 2026 18:20:51 -0500 Subject: [PATCH 28/42] xo-indentlog: bugfix: lazy lpos b/c state may be stale. This is because xputc() only *might* be called. ostream may update streambuf's buffer without calling streambuf virtual methods --- include/xo/indentlog/log_streambuf.hpp | 215 ++++++++++++++++++++----- utest/log_streambuf.test.cpp | 77 ++++++++- 2 files changed, 243 insertions(+), 49 deletions(-) diff --git a/include/xo/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp index b6e9305d..36166fce 100644 --- a/include/xo/indentlog/log_streambuf.hpp +++ b/include/xo/indentlog/log_streambuf.hpp @@ -19,7 +19,7 @@ namespace xo { 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} {} + : solpos{solpos}, color_escape_chars{color_esc}, pos{p} {} std::size_t solpos = 0; std::size_t color_escape_chars = 0; @@ -33,17 +33,39 @@ namespace xo { } /*ctor*/ std::streamsize capacity() const { return this->buf_v_.size(); } - char const * lo() const { return this->pbase(); } - char const * hi() const { return this->lo() + this->capacity(); } + const char * lo() const { return this->pbase(); } + const char * hi() const { return this->lo() + this->capacity(); } std::uint32_t pos() const { return this->pptr() - this->pbase(); } + /** output position (relative to pbase) when local state last computed. Exposed here for unit tests **/ + const char * _local_ppos() const { return local_ppos_; } + /** position (relative to pbase) one character after last \n or \r. For unit tests **/ + std::uint32_t _solpos() const { return solpos_; } + /** start of incomplete color-escape sequence **/ + const char * _color_escape_start() const { return color_escape_start_; } + /** number of non-printing chars after @ref solpos_ from completed color-escape sequences **/ + std::uint32_t _color_escape_chars() const { return color_escape_chars_; } + /** number of visible characters since start of line (last \n or \r) **/ std::uint32_t lpos() const { - assert(pos() >= solpos_ + color_escape_chars_); + if (debug_flag_) { + std::cerr << "log_streambuf::lpos: enter" << std::endl; + } + + // logically-const. lazy implementation + log_streambuf * self = const_cast(this); + + self->_check_update_local_state(); + return pos() - solpos_ - color_escape_chars_; } rewind_state checkpoint() const { + // logically-const. lazy implementation + log_streambuf * self = const_cast(this); + + self->_check_update_local_state(); + return rewind_state(solpos_, color_escape_chars_, pos()); } @@ -56,6 +78,7 @@ namespace xo { /* tells parent our buffer extent */ this->setp(p_lo, p_hi); + this->local_ppos_ = 0; this->solpos_ = 0; this->color_escape_chars_ = 0; this->color_escape_start_ = nullptr; @@ -69,12 +92,16 @@ namespace xo { << std::endl; } - /* .setp(): using for side effect: sets .pptr to .pbase */ + /* .setp(): using just for side effect: sets .pptr to .pbase */ this->setp(this->pbase(), this->epptr()); + /* advance pptr to saved position */ this->pbump(s.pos); + this->local_ppos_ = this->pptr() - this->pbase(); this->solpos_ = s.solpos; this->color_escape_chars_ = s.color_escape_chars; + /* assuming we never try to capture rewind state with incomplete color escape */ + this->color_escape_start_ = nullptr; } protected: @@ -87,6 +114,8 @@ namespace xo { assert(old_n <= static_cast(buf_v_.size())); assert(new_z > buf_v_.capacity()); + /* note: local_ppos_ invariant across expand_to() */ + this->buf_v_.resize(new_z); char * p_base = &(this->buf_v_[0]); @@ -132,51 +161,21 @@ namespace xo { std::memcpy(this->pptr(), s, ncopied); - /* 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()); - /* 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; - } - } - } - this->pbump(ncopied); + /* now {pbase, pptr} consistent with new input */ + + this->_check_update_local_state(); + return ncopied; } /*xsputn*/ virtual int_type overflow(int_type new_ch) override { + char * old_base = this->pbase(); char * old_pptr = this->pptr(); - std::streamsize old_n = old_pptr - this->pbase(); + /* #of chars buffered */ + std::streamsize old_n = old_pptr - old_base; assert(old_n <= static_cast(this->buf_v_.size())); @@ -184,6 +183,7 @@ namespace xo { std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl; } + /* increase buffer size */ this->expand_to(2 * buf_v_.size()); this->buf_v_[old_n] = new_ch; @@ -191,6 +191,8 @@ namespace xo { if ((new_ch == static_cast('\n')) || (new_ch == static_cast('\r'))) { this->solpos_ = this->pos(); + + // what if new_ch starts color escape ? } if (new_ch == Traits::eof()) { @@ -235,8 +237,130 @@ namespace xo { } /*seekoff*/ private: + void _update_local_state_char(const char * p_lo, const char * p) + { + if ((*p == '\n') || (*p == '\r')) { + this->solpos_ = (p+1 - this->pbase()); + /* 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_) [[unlikely]] { + 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_) [[unlikely]] { + 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; + } + } + } + + /** recognize stale local state vars: + * @ref solpos_, @ref color_escape_chars_, @ref color_escape_start_. + * + * Require: + * - {pbase, pptr} in consistent state + * Promise: + * - @c local_ppos_ + @c pbase = @c pptr + * - @c solpos_, @c color_escape_chars_, @c color_escape_start_ all up-to-date + **/ + void _check_update_local_state() { + const char * p0 = this->pbase(); + const char * pn = this->pptr(); + + if (debug_flag_) { + std::cerr << "_check_update_local_state:" << std::endl; + std::cerr << " buf: (p0=" << (void*)p0 << ", pn=" << (void*)pn << ")" << std::endl; + std::cerr << " solpos_=" << solpos_ << ", color_escape_chars_=" << color_escape_chars_ << std::endl; + } + + if (p0 + local_ppos_ == pn) [[likely]] { + // solpos_, color_escape_chars_, color_escape_start_ all up-to-date + } else { + // [pnew, pn): input that hasn't been incorporated into + // {solpos_, color_escape_chars_, color_escape_start_) + + const char * pnew = this->pbase() + this->local_ppos_; + + if (debug_flag_) { + std::cerr << "_check_update_local_state: range: (pnew=" << (void*)pnew << ", pn=" << (void*)pn << ")" << std::endl; + } + + for(const char * p = pnew; p < pn; ++p) { + this->_update_local_state_char(p0, p); + } + } + + // solpos_, color_escape_chars_, color_escape_start_ all up-to-date + // for current buffered contents + + this->local_ppos_ = pn - p0; + + if (debug_flag_) { + std::cerr << "_check_update_local_state: pos=" << pos(); + std::cerr << ", solpos=" << solpos_; + std::cerr << ", color_escape_chars=" << color_escape_chars_ << std::endl; + } + + assert(pos() >= solpos_ + color_escape_chars_); + } + + private: + /* + * pbase: start of buffered text. Thils will be address of buf_v_[0] + * + * + * pbase pptr epptr + * v >e1< >e2< v v + * |xx\xxEEExxx\xxxxxxxEExxxxEExxxxxxxEExxx\xEExxxxxx..................| + * ^ ^<------new-------> + * solpos local_ppos + * + * solpos : first character after newline (stale) + * color_escape_pos : e1+e2+.. (stale) + * new : new characters not reflected + * in local_ppos_, color_escape_chars_ etc. + * + * Legend: + * [\] newline + * [x] visible character + * [E] color escape chars + * + * + * after _check_update_local_state(): + * + * + * pbase pptr epptr + * v >e1< v v + * |xx\xxEEExxx\xxxxxxxEExxxxEExxxxxxxEExxx\xEExxxxxx..................| + * ^ ^ + * solpos local_ppos + * + */ + + /** @defgroup logstreambuf-instance-vars **/ + ///@{ + + /** value of pptr (relative to pbase) when _check_update_local_state() last ran **/ + std::size_t local_ppos_ = 0; /** position (relative to pbase) one character after last \n or \r. - * Use to drive @ref lpos + * Use to drive @ref lpos. This _has_ to be lazy, since + * xsputn() isn'g guaranteed to be called when there's room in + * in buffer. **/ std::size_t solpos_ = 0; /** number of non-printing chars after @ref solpos_, from @@ -245,11 +369,14 @@ namespace xo { **/ std::size_t color_escape_chars_ = 0; /** non-null: start of incomplete color escape sequence **/ - char const * color_escape_start_ = nullptr; + const char * color_escape_start_ = nullptr; + /** buffered output stored here **/ std::vector buf_v_; /** true to debug log_streambuf itself **/ bool debug_flag_ = false; + + ///@} }; /*log_streambuf*/ } /*namespace xo*/ diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index 8098d881..656d8b97 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -1,30 +1,97 @@ /* @file log_streambuf.test.cpp */ -#include "xo/indentlog/log_streambuf.hpp" -#include "xo/indentlog/print/tag.hpp" -#include "xo/indentlog/print/quoted.hpp" +#include "scope.hpp" +#include "log_streambuf.hpp" +#include "print/tag.hpp" +#include "print/quoted.hpp" #include //#include namespace ut { + using xo::xtag; + using xo::scope; using xo::log_streambuf; + using std::cerr; + using std::endl; + + TEST_CASE("log_streamhuf", "[log_streambuf]") + { + constexpr bool c_debug_flag = false; + //scope log(XO_DEBUG(c_debug_flag), "log_streambuf test"); - TEST_CASE("log_streamhuf", "[log_streambuf]") { std::size_t z = 16; - log_streambuf> sbuf(z); + log_streambuf> sbuf(z, c_debug_flag); std::ostream ss(&sbuf); + REQUIRE(sbuf.debug_flag() == c_debug_flag); + + if (c_debug_flag) { + cerr << "empty log_streambuf" << endl; + cerr << "sbuf.lo=" << (void*)sbuf.lo() << endl; + cerr << "sbuf.hi=" << (void*)sbuf.hi() << endl; + cerr << "sbuf._color_escape_chars=" << sbuf._color_escape_chars() << endl; + } + + //REQUIRE(sbuf.lo() == sbuf.pbase()); + REQUIRE(sbuf.capacity() == z); REQUIRE(sbuf.pos() == 0); + REQUIRE(sbuf._solpos() == 0); REQUIRE(sbuf.lpos() == 0); + // note: log_streambuf doesn't get control on every character + ss << '\n'; + if (c_debug_flag) { + cerr << "after single newline" << endl; + cerr << "sbuf.lo=" << (void*)sbuf.lo() << endl; + cerr << "sbuf.hi=" << (void*)sbuf.hi() << endl; + cerr << "sbuf._color_escape_chars=" << sbuf._color_escape_chars() << endl; + } + REQUIRE(sbuf.capacity() == z); REQUIRE(sbuf.pos() == 1); + // we don't know what sbuf._solpos is. Could be 0 or 1 depending on + // ostream implementation + bool ok = (sbuf._solpos() == 0) || (sbuf._solpos() == 1); + REQUIRE(ok); REQUIRE(sbuf.lpos() == 0); // at least on OSX } + TEST_CASE("log_streambuf_xtag", "[log_streambuf][xtag]") + { + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag), "log_streambuf_xtag test"); + + std::size_t z = 16; + log_streambuf> sbuf(z, false); + std::ostream ss(&sbuf); + + ss <<"empty log_streambuf"; + ss << xtag("sbuf.lo", (void*)sbuf.lo()); + ss << xtag("sbuf.hi", (void*)sbuf.hi()); + ss << xtag("sbuf.color_escape_chars", sbuf._color_escape_chars()); + + //REQUIRE(sbuf.lo() == sbuf.pbase()); + + /* sbuf size will have been expanded */ + REQUIRE(sbuf.capacity() == 128); + REQUIRE(sbuf.pos() == 82); + REQUIRE(sbuf._solpos() == 0); + REQUIRE(sbuf.lpos() == 82); + + // note: log_streambuf doesn't get control on every character + + ss << '\n'; + + REQUIRE(sbuf.capacity() == 128); + REQUIRE(sbuf.pos() == 83); + REQUIRE(sbuf.lpos() == 0); + // note: solpos: updated b/c of call to lazy sbuf.lpos() + REQUIRE(sbuf._solpos() == 83); + } + // write test cases with some random strings. // for each string a list of tuples {ch,pos,lpos} From 6ccb69c6934b0781029720fa6c47f9422a7e8134 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 12:40:26 -0500 Subject: [PATCH 29/42] xo-reader2: + pretty-printing for ParserResult + use in utest --- include/xo/indentlog/print/ppconfig.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/xo/indentlog/print/ppconfig.hpp b/include/xo/indentlog/print/ppconfig.hpp index 67d1831a..10c89639 100644 --- a/include/xo/indentlog/print/ppconfig.hpp +++ b/include/xo/indentlog/print/ppconfig.hpp @@ -16,6 +16,21 @@ namespace xo { * Need one read-only instance of this to invoke pretty printer **/ struct ppconfig { + /** @defgroup ppconfig-ctors ppconfig constructors **/ + ///@{ + + /** config to use pretty printer in degenerate form: + * In this form right margin is absurdly far away, + * so should not be forced to use multiple lines. + * + * Note this won't prevent a printer from returning -1 + **/ + static inline ppconfig ugly() { + return ppconfig { .right_margin_ = 99999999, .indent_width_ = 0, .assert_indent_threshold = 10 }; + } + + ///@} + /** @defgroup ppconfig-instance-vars ppconfig instance variables **/ ///@{ From a537cb3d1f2c083deb1f89d739cd4110dbef69aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 16:40:23 -0500 Subject: [PATCH 30/42] xo-indentlog: fix log_streambuf unit test + string_view operator --- include/xo/indentlog/log_streambuf.hpp | 3 +++ utest/log_streambuf.test.cpp | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/xo/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp index 36166fce..e9672f47 100644 --- a/include/xo/indentlog/log_streambuf.hpp +++ b/include/xo/indentlog/log_streambuf.hpp @@ -4,6 +4,7 @@ #include "print/quoted_char.hpp" #include +#include #include #include // e.g. for std::memcpy() #include @@ -71,6 +72,8 @@ namespace xo { bool debug_flag() const { return debug_flag_; } + operator std::basic_string_view () const { return std::basic_string_view(this->pbase(), this->pptr()); } + void reset_stream() { char * p_lo = &(this->buf_v_[0]); char * p_hi = p_lo + this->capacity(); diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index 656d8b97..fc3968be 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -5,12 +5,14 @@ #include "print/tag.hpp" #include "print/quoted.hpp" #include +#include //#include namespace ut { using xo::xtag; using xo::scope; using xo::log_streambuf; + using std::string_view; using std::cerr; using std::endl; @@ -68,28 +70,32 @@ namespace ut { log_streambuf> sbuf(z, false); std::ostream ss(&sbuf); - ss <<"empty log_streambuf"; - ss << xtag("sbuf.lo", (void*)sbuf.lo()); - ss << xtag("sbuf.hi", (void*)sbuf.hi()); + ss << "empty log_streambuf"; + ss << xtag("sbuf.lo", (void*)nullptr); + ss << xtag("sbuf.hi", (void*)nullptr); ss << xtag("sbuf.color_escape_chars", sbuf._color_escape_chars()); //REQUIRE(sbuf.lo() == sbuf.pbase()); + auto expected = string_view("empty log_streambuf :sbuf.lo 0 :sbuf.hi 0 :sbuf.color_escape_chars 0"); + + REQUIRE(string_view(sbuf) == expected); + /* sbuf size will have been expanded */ REQUIRE(sbuf.capacity() == 128); - REQUIRE(sbuf.pos() == 82); + REQUIRE(sbuf.pos() == expected.size()); REQUIRE(sbuf._solpos() == 0); - REQUIRE(sbuf.lpos() == 82); + REQUIRE(sbuf.lpos() == expected.size()); // note: log_streambuf doesn't get control on every character ss << '\n'; REQUIRE(sbuf.capacity() == 128); - REQUIRE(sbuf.pos() == 83); + REQUIRE(sbuf.pos() == expected.size() + 1); REQUIRE(sbuf.lpos() == 0); // note: solpos: updated b/c of call to lazy sbuf.lpos() - REQUIRE(sbuf._solpos() == 83); + REQUIRE(sbuf._solpos() == expected.size() + 1); } // write test cases with some random strings. From 94dcbc1677f1abc5585d3794348f433e022d510e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 16:40:43 -0500 Subject: [PATCH 31/42] xo-indentlog: + cond feature --- include/xo/indentlog/print/cond.hpp | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 include/xo/indentlog/print/cond.hpp diff --git a/include/xo/indentlog/print/cond.hpp b/include/xo/indentlog/print/cond.hpp new file mode 100644 index 00000000..de3a13e7 --- /dev/null +++ b/include/xo/indentlog/print/cond.hpp @@ -0,0 +1,92 @@ +/** @file cond.hpp + * + * author: Roland Conybeare + **/ + +#pragma once + +#include "ppdetail_atomic.hpp" +#include +#include + +namespace xo { + + /** @class cond_impl + * @brief conditional stream inserter implementation + * + * @tparam T type printed when condition is true + * @tparam U type printed when condition is false + **/ + template + struct cond_impl { + public: + cond_impl(bool condition, T const & then_value, U const & else_value) + : condition_{condition}, + then_value_{then_value}, + else_value_{else_value} {} + + bool condition() const { return condition_; } + T const & then_value() const { return then_value_; } + U const & else_value() const { return else_value_; } + + private: + bool condition_; + T then_value_; + U else_value_; + }; + + /** Create conditional stream inserter. + * + * @param condition when true, print @p then_value; otherwise print @p else_value + * @param then_value value to print when condition is true + * @param else_value value to print when condition is false + * + * Example: + * @code + * std::cout << cond(ptr != nullptr, xtag("ptr", *ptr), "nullptr"); + * @endcode + **/ + template + auto + cond(bool condition, T && then_value, U && else_value) + { + return cond_impl, std::decay_t> + (condition, + std::forward(then_value), + std::forward(else_value)); + } + + /** Stream insertion operator for cond_impl **/ + template + inline std::ostream & + operator<<(std::ostream & os, cond_impl const & c) + { + if (c.condition()) + os << c.then_value(); + else + os << c.else_value(); + return os; + } + +#ifndef ppdetail_atomic + namespace print { + /** Pretty-printing support for cond_impl **/ + template + struct ppdetail> { + using target_type = cond_impl; + + static bool print_pretty(const ppindentinfo & ppii, + const target_type & c) + { + if (c.condition()) + return ppdetail::print_pretty(ppii, c.then_value()); + else + return ppdetail::print_pretty(ppii, c.else_value()); + } + }; + } +#endif + +} + +/* end cond.hpp */ From 310dabde67febb13d910031fa888d5598eedb065 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 16:45:40 -0500 Subject: [PATCH 32/42] xo-indentlog: cosmetic adj in cond.hpp --- include/xo/indentlog/print/cond.hpp | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/include/xo/indentlog/print/cond.hpp b/include/xo/indentlog/print/cond.hpp index de3a13e7..47883bfa 100644 --- a/include/xo/indentlog/print/cond.hpp +++ b/include/xo/indentlog/print/cond.hpp @@ -20,51 +20,51 @@ namespace xo { template struct cond_impl { public: - cond_impl(bool condition, T const & then_value, U const & else_value) + cond_impl(bool condition, const T & if_true, const U & if_false) : condition_{condition}, - then_value_{then_value}, - else_value_{else_value} {} + if_true_{if_true}, + if_false_{if_false} {} bool condition() const { return condition_; } - T const & then_value() const { return then_value_; } - U const & else_value() const { return else_value_; } + const T & if_true() const { return if_true_; } + const U & if_false() const { return if_false_; } private: bool condition_; - T then_value_; - U else_value_; + T if_true_; + U if_false_; }; /** Create conditional stream inserter. * - * @param condition when true, print @p then_value; otherwise print @p else_value - * @param then_value value to print when condition is true - * @param else_value value to print when condition is false + * @param condition when true, print @p if_true; otherwise print @p if_false + * @param if_true value to print when condition is true + * @param if_false value to print when condition is false * * Example: * @code - * std::cout << cond(ptr != nullptr, xtag("ptr", *ptr), "nullptr"); + * std::cout << cond(ptr, xtag("ptr", *ptr), "nullptr"); * @endcode **/ template auto - cond(bool condition, T && then_value, U && else_value) + cond(bool condition, T && if_true, U && if_false) { return cond_impl, std::decay_t> (condition, - std::forward(then_value), - std::forward(else_value)); + std::forward(if_true), + std::forward(if_false)); } /** Stream insertion operator for cond_impl **/ template inline std::ostream & - operator<<(std::ostream & os, cond_impl const & c) + operator<<(std::ostream & os, const cond_impl & c) { if (c.condition()) - os << c.then_value(); + os << c.if_true(); else - os << c.else_value(); + os << c.if_false(); return os; } @@ -79,9 +79,9 @@ namespace xo { const target_type & c) { if (c.condition()) - return ppdetail::print_pretty(ppii, c.then_value()); + return ppdetail::print_pretty(ppii, c.if_true()); else - return ppdetail::print_pretty(ppii, c.else_value()); + return ppdetail::print_pretty(ppii, c.if_false()); } }; } From 922d250f36e67367958ab107bb393bfa88ee723d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 16:49:52 -0500 Subject: [PATCH 33/42] xo-indentlog: + cond utest --- utest/CMakeLists.txt | 2 +- utest/cond.test.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 utest/cond.test.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index c8ea729c..5fa2bf1b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_EXECUTABLE_NAME utest.indentlog) set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp timeutil.test.cpp tag.test.cpp filename.test.cpp code_location.test.cpp function.test.cpp pretty_vector.test.cpp - indentlog_utest_main.cpp log_streambuf.test.cpp toppstr.test.cpp) + indentlog_utest_main.cpp log_streambuf.test.cpp toppstr.test.cpp cond.test.cpp) xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) diff --git a/utest/cond.test.cpp b/utest/cond.test.cpp new file mode 100644 index 00000000..e421c642 --- /dev/null +++ b/utest/cond.test.cpp @@ -0,0 +1,47 @@ +/* @file cond.test.cpp */ + +#include "xo/indentlog/print/cond.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + TEST_CASE("cond", "[cond]") { + tag_config::tag_color = color_spec_type::none(); + + { + std::stringstream ss; + ss << cond(true, "yes", "no"); + REQUIRE(ss.str() == "yes"); + } + + { + std::stringstream ss; + ss << cond(false, "yes", "no"); + REQUIRE(ss.str() == "no"); + } + + { + std::stringstream ss; + ss << cond(true, 42, "none"); + REQUIRE(ss.str() == "42"); + } + + { + std::stringstream ss; + ss << cond(false, 42, "none"); + REQUIRE(ss.str() == "none"); + } + + { + std::stringstream ss; + int * ptr = nullptr; + ss << cond(ptr != nullptr, xtag("ptr", 123), xtag("ptr", "null")); + REQUIRE(ss.str() == " :ptr null"); + } + } +} + +/* end cond.test.cpp */ From 8ed5f5b995cca038d259a75e956416e4f2727238 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Jan 2026 16:51:25 -0500 Subject: [PATCH 34/42] xo-indentlog: +1 more cond utest --- utest/cond.test.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/utest/cond.test.cpp b/utest/cond.test.cpp index e421c642..d360a449 100644 --- a/utest/cond.test.cpp +++ b/utest/cond.test.cpp @@ -37,8 +37,21 @@ namespace ut { { std::stringstream ss; + int * ptr = nullptr; - ss << cond(ptr != nullptr, xtag("ptr", 123), xtag("ptr", "null")); + + ss << cond(ptr, xtag("ptr", 123), xtag("ptr", "null")); + + REQUIRE(ss.str() == " :ptr null"); + } + + { + std::stringstream ss; + + int * ptr = nullptr; + + ss << xtag("ptr", cond(ptr, 123, "null")); + REQUIRE(ss.str() == " :ptr null"); } } From 221402fcbc7e5b0254d871a9bd20049e4888b635 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 2 Feb 2026 21:55:34 -0500 Subject: [PATCH 35/42] xo-interpreter2: scaffold repl + alloc measurement frameowkr --- include/xo/indentlog/log_state.hpp | 151 ----------------------------- 1 file changed, 151 deletions(-) diff --git a/include/xo/indentlog/log_state.hpp b/include/xo/indentlog/log_state.hpp index e28fb2c4..461f2c7b 100644 --- a/include/xo/indentlog/log_state.hpp +++ b/include/xo/indentlog/log_state.hpp @@ -255,157 +255,6 @@ namespace xo { // instead of post-processing, rely on newline-aware log_streambuf // to indent in advance -#ifdef OBSOLETE - log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get(); - - /* often sbuf contains one line of output. - * if it contains multiple newlines, need to indent - * after each one. - * - * will scan output in *sbuf1, post-process to *sbuf2, - * then write *sbuf2 to output stream - * - * note: we inherit .lpos from prec call to .flush2sbuf(), - * in the unlikely event that it's non-zero - */ - char const * s = sbuf1->lo(); - - char const * e = s + sbuf1->pos(); - char const * p = s; - - /* point to first space following a non-space character. - * will indent to just after this space - */ - char const * space_after_nonspace = nullptr; - - /* true on VT100 color escape (\033); in which case false on terminating char (m) - * don't advance lpos during escape - */ - bool in_color_escape = false; - - while(true) { - bool have_nonspace = false; - - /* invariant: s<=p<=e */ - - /* for indenting, looking for first 'space following non-space, on first line', if any */ - -#ifdef OBSOLETE - // ..multiline input should have already been indented by custom log_streambuf. - // may need to extend to recognize terminal control sequences like below - - std::size_t lpos_on_newline = 0; -#endif - -#ifdef OBSOLETE - while(p < e) { - if(space_after_nonspace) { - ; - } else { - if(*p != ' ') - have_nonspace = true; - - if(have_nonspace && (*p == ' ')) { - space_after_nonspace = p; - } - } - - if (in_color_escape && (*p != '\n')) { - /* in color escape -> don't advance .lpos */ - if (*p == 'm') - in_color_escape = false; - ++p; - } else if (*p == '\033') { - /* begin color escape sequence */ - in_color_escape = true; - ++p; - } else if (*p == '\n') { - /* reset .pos on newline; also drop any (incomplete + ill-formed) color escape */ - - in_color_escape = false; - -#ifdef OBSOLETE - lpos_on_newline = this->lpos_; - this->lpos_ = 0; -#endif - - ++p; - break; - } else { - /* increment .lpos on non-newline */ - ++(this->lpos_); - ++p; - } - } -#endif - - /* p=e or *p=\n */ - - /* charseq [s,p) does not contain any newlines, print it */ - if (lpos_on_newline > 0) { - /* charseq [s,p) does not contain any newlines, print it */ - sbuf2->sputn(s, p - s - 1); - - if (this->location_flag_) { - /* 'tab' to position lpos for [file:line] */ - sbuf2->sputc(' '); - for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i) - sbuf2->sputc(' '); - - std::stringstream ss; - ss << code_location(this->file_, this->line_, - log_config::code_location_color); - - std::string ss_str = ss.str(); /*hoping for copy elision here*/ - sbuf2->sputn(ss_str.c_str(), ss_str.size()); - - this->location_flag_ = false; - this->file_ = ""; - this->line_ = 0; - } - - sbuf2->sputc('\n'); - } else { - /* control here if .flush2sbuf() called without trailing newline in .p_sbuf_phase1 */ - sbuf2->sputn(s, p - s); - } - - if (p == e) - break; - - // { - // char buf[80]; - // snprintf(buf, sizeof(buf), "*** indent=[%d] next=[%c]", this->nesting_level_, *(p+1)); - // - // std::clog.rdbuf()->sputn(buf, strlen(buf)); - //} - - /* control here only for continuation lines (application logging code embedding its own newlines) - * - minimum indent = nesting level; - * - however if space_after_nonspace defined, also indent for that - */ - std::uint32_t n_indent = 0; - - n_indent += this->calc_time_indent(); - - n_indent += std::min(this->nesting_level_ * log_config::indent_width, - log_config::max_indent_width); - -#ifdef OBSOLETE // nice try, broken for multiline input + written before log_streambuf calculated lpos - /* this is just to indent for per-line entry/exit label */ - if(space_after_nonspace) - n_indent += (space_after_nonspace - s); -#endif - - for(std::uint32_t i = 0; i < n_indent; ++i) - sbuf2->sputc(' '); - - s = p; - } - - /* now write entire contents of *sbuf2 to clog */ - p_sbuf->sputn(sbuf2->lo(), sbuf2->pos()); -#endif p_sbuf->sputn(sbuf1->lo(), sbuf1->pos()); /* reset streams for next message */ From 98f860be4d183f5281a0af5d95f7c75f9fca1668 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Feb 2026 10:31:42 -0500 Subject: [PATCH 36/42] xo-indentlog: presence flag for refrtag/xrefrtag --- include/xo/indentlog/print/pretty.hpp | 17 ++++++++++++++++ include/xo/indentlog/print/tag.hpp | 28 +++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/include/xo/indentlog/print/pretty.hpp b/include/xo/indentlog/print/pretty.hpp index 3ae010eb..d215b8b3 100644 --- a/include/xo/indentlog/print/pretty.hpp +++ b/include/xo/indentlog/print/pretty.hpp @@ -256,6 +256,11 @@ namespace xo { Member && member, Rest&&... rest) { + if constexpr (has_present>) { + if (!member.present()) + return this->print_upto_struct_members(ppii, rest...); + } + if (this->print_upto(" ") && this->print_upto(member)) return this->print_upto_struct_members(ppii, rest...); @@ -268,6 +273,13 @@ namespace xo { Member && member, Rest&&... rest) { + if constexpr (has_present>) { + if (!member.present()) { + this->pretty_struct_members(ppii, rest...); + return; + } + } + newline_indent(ppii.ci1()); this->pretty(member); this->pretty_struct_members(ppii, rest...); @@ -381,6 +393,11 @@ namespace xo { static bool print_pretty(const ppindentinfo & ppii, const Tag & tag) { + if constexpr (has_present) { + if (!tag.present()) + return true; + } + if (ppii.upto()) { if (tag.prefix_space()) ppii.pps()->write(" "); diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index 026e8205..03a03223 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -6,6 +6,7 @@ #include "concat.hpp" #include "quoted.hpp" #include "color.hpp" +#include #include // STRINGIFY(xyz) -> "xyz" @@ -20,6 +21,12 @@ #define XTAG(x) xo::xtag(STRINGIFY(x), x) namespace xo { + /** concept: true if T has a .present() method returning something bool-like **/ + template + concept has_present = requires(const T& t) { + { t.present() } -> std::convertible_to; + }; + enum class tagstyle { /** print with automatic escapes for embedded special characters * (any of ' ','"','\','\n','\r','\t'). @@ -79,10 +86,13 @@ namespace xo { **/ 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)} {} + ref_tag_impl(Name const & n, Value const & v, bool present = true) + : name_{n}, value_{v}, present_{present} {} + ref_tag_impl(Name && n, Value && v, bool present = true) + : name_{std::forward(n)}, value_{std::forward(v)}, present_{present} {} constexpr bool prefix_space() const { return PrefixSpace; } + bool present() const { return present_; } Name const & name() const { return name_; } Value const & value() const { return value_; } @@ -90,6 +100,7 @@ namespace xo { private: Name name_; const Value& value_; + bool present_ = true; }; // ----- xtag ----- @@ -150,12 +161,14 @@ namespace xo { /** '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) + * + * Print nothing if @p present is false **/ template auto - refrtag(Name && n, Value && v) + refrtag(Name && n, Value && v, bool present = true) { - return ref_tag_impl, std::decay_t>(n, v); + return ref_tag_impl, std::decay_t>(n, v, present); } // ----- xrefrtag ----- @@ -164,9 +177,9 @@ namespace xo { **/ template auto - xrefrtag(Name && n, Value && v) + xrefrtag(Name && n, Value && v, bool present = true) { - return ref_tag_impl, std::decay_t>(n, v); + return ref_tag_impl, std::decay_t>(n, v, present); } // ----- operator<< on tag_impl ----- @@ -199,6 +212,9 @@ namespace xo { operator<<(std::ostream &s, ref_tag_impl const & tag) { + if (!tag.present()) + return s; + using xo::print::unq; if (PrefixSpace) From 49decd246dcd24369ea5f2ae637fbdf299d304c6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 25 Feb 2026 17:05:45 +1100 Subject: [PATCH 37/42] indentlog: fix utest for OSX --- utest/log_streambuf.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index fc3968be..1e38a01e 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -77,7 +77,7 @@ namespace ut { //REQUIRE(sbuf.lo() == sbuf.pbase()); - auto expected = string_view("empty log_streambuf :sbuf.lo 0 :sbuf.hi 0 :sbuf.color_escape_chars 0"); + auto expected = string_view("empty log_streambuf :sbuf.lo 0x0 :sbuf.hi 0x0 :sbuf.color_escape_chars 0"); REQUIRE(string_view(sbuf) == expected); From 1a5aae6a17446848c6f42ae7bedb4a8616c256a3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 26 Feb 2026 00:47:49 +1100 Subject: [PATCH 38/42] xo-arena: nix-build works, including utest --- utest/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 5fa2bf1b..b22b4eb8 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -6,12 +6,10 @@ set(SELF_SOURCE_FILES filename.test.cpp code_location.test.cpp function.test.cpp pretty_vector.test.cpp indentlog_utest_main.cpp log_streambuf.test.cpp toppstr.test.cpp cond.test.cpp) -xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) - -# ---------------------------------------------------------------- -# 3rd party dependency: catch2 - -xo_self_dependency(${SELF_EXECUTABLE_NAME} indentlog) -xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) +if (ENABLE_TESTING) + xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) + xo_self_dependency(${SELF_EXECUTABLE_NAME} indentlog) + xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) +endif() # end CMakeLists.txt From 524bc5b26062e1ad80e43374bfde245b2c67f852 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 27 Feb 2026 19:38:53 +1100 Subject: [PATCH 39/42] xo-cmake: setup to make share target available via cmake install --- cmake/indentlogConfig.cmake.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/indentlogConfig.cmake.in b/cmake/indentlogConfig.cmake.in index cc57615e..38c9e093 100644 --- a/cmake/indentlogConfig.cmake.in +++ b/cmake/indentlogConfig.cmake.in @@ -1,4 +1,5 @@ @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") check_required_components("@PROJECT_NAME@") From d60b74d2b52b3ab878642bf0d069f2bd5b255137 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Mar 2026 12:24:27 -0400 Subject: [PATCH 40/42] xo-indentlog: utest: carveout for osx-vs-linux nullptr printing --- utest/log_streambuf.test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utest/log_streambuf.test.cpp b/utest/log_streambuf.test.cpp index 1e38a01e..94d20195 100644 --- a/utest/log_streambuf.test.cpp +++ b/utest/log_streambuf.test.cpp @@ -77,7 +77,11 @@ namespace ut { //REQUIRE(sbuf.lo() == sbuf.pbase()); +#ifdef __linux__ + auto expected = string_view("empty log_streambuf :sbuf.lo 0 :sbuf.hi 0 :sbuf.color_escape_chars 0"); +#else auto expected = string_view("empty log_streambuf :sbuf.lo 0x0 :sbuf.hi 0x0 :sbuf.color_escape_chars 0"); +#endif REQUIRE(string_view(sbuf) == expected); From 332e4b46ad01a53bb2daf245bc0df2aa97fc8bdd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 May 2026 08:27:10 -0400 Subject: [PATCH 41/42] xo-alloc2: + utest harness for catch2 accept additional commandline arguments --- include/xo/indentlog/scope.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/xo/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp index 42a664c9..8ed57497 100644 --- a/include/xo/indentlog/scope.hpp +++ b/include/xo/indentlog/scope.hpp @@ -125,6 +125,20 @@ namespace xo { public: template basic_scope(scope_setup setup, Tn&&... rest); + basic_scope(basic_scope && other) noexcept + : dest_sbuf_{other.dest_sbuf_}, + style_{other.style_}, + name1_{other.name1_}, + name2_{other.name2_}, + file_{other.file_}, + line_{other.line_}, + finalized_{other.finalized_} + { + other.finalized_ = true; + } + basic_scope(const basic_scope &) = delete; + basic_scope & operator=(const basic_scope &) = delete; + basic_scope & operator=(basic_scope &&) = delete; ~basic_scope(); bool enabled() const { return !finalized_; } From 0834c6adb0ec892726e014672c18c73072b7c2a7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 31 May 2026 09:50:13 -0400 Subject: [PATCH 42/42] build: update cmake bootstrap macros for in-tree builds Once these are done for all xo-foo satellites, should be able to complete cmake build without first installing xo-cmake. --- cmake/xo-bootstrap-macros.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index aba31169..592272c0 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -19,7 +19,13 @@ endif() message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") -if (NOT XO_SUBMODULE_BUILD) +if (XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # local version of xo-cmake macros + set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/xo-cmake/cmake") + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +else() if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) # default to typical install location for xo-project-macros execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)